diff --git a/graphql/documents/data/scene-slim.graphql b/graphql/documents/data/scene-slim.graphql index a76c73070..60fa93fe6 100644 --- a/graphql/documents/data/scene-slim.graphql +++ b/graphql/documents/data/scene-slim.graphql @@ -6,6 +6,7 @@ fragment SlimSceneData on Scene { url date rating + o_counter path file { diff --git a/graphql/documents/data/scene.graphql b/graphql/documents/data/scene.graphql index 69d516286..9157eb714 100644 --- a/graphql/documents/data/scene.graphql +++ b/graphql/documents/data/scene.graphql @@ -6,6 +6,7 @@ fragment SceneData on Scene { url date rating + o_counter path file { diff --git a/graphql/documents/mutations/scene.graphql b/graphql/documents/mutations/scene.graphql index dac4a9970..ad4076c81 100644 --- a/graphql/documents/mutations/scene.graphql +++ b/graphql/documents/mutations/scene.graphql @@ -62,6 +62,18 @@ mutation ScenesUpdate($input : [SceneUpdateInput!]!) { } } +mutation SceneIncrementO($id: ID!) { + sceneIncrementO(id: $id) +} + +mutation SceneDecrementO($id: ID!) { + sceneDecrementO(id: $id) +} + +mutation SceneResetO($id: ID!) { + sceneResetO(id: $id) +} + mutation SceneDestroy($id: ID!, $delete_file: Boolean, $delete_generated : Boolean) { sceneDestroy(input: {id: $id, delete_file: $delete_file, delete_generated: $delete_generated}) } \ No newline at end of file diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index 099fc2d78..8feae6add 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -107,6 +107,13 @@ type Mutation { sceneDestroy(input: SceneDestroyInput!): Boolean! scenesUpdate(input: [SceneUpdateInput!]!): [Scene] + """Increments the o-counter for a scene. Returns the new value""" + sceneIncrementO(id: ID!): Int! + """Decrements the o-counter for a scene. Returns the new value""" + sceneDecrementO(id: ID!): Int! + """Resets the o-counter for a scene to 0. Returns the new value""" + sceneResetO(id: ID!): Int! + sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker sceneMarkerDestroy(id: ID!): Boolean! diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index 7a7f356b5..4849d2803 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -62,6 +62,8 @@ input SceneMarkerFilterType { input SceneFilterType { """Filter by rating""" rating: IntCriterionInput + """Filter by o-counter""" + o_counter: IntCriterionInput """Filter by resolution""" resolution: ResolutionEnum """Filter by duration (in seconds)""" diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index 7e6764630..c0828dae4 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -26,6 +26,7 @@ type Scene { url: String date: String rating: Int + o_counter: Int path: String! file: SceneFileType! # Resolver diff --git a/pkg/api/resolver_mutation_scene.go b/pkg/api/resolver_mutation_scene.go index 9b86af534..f419d3b3c 100644 --- a/pkg/api/resolver_mutation_scene.go +++ b/pkg/api/resolver_mutation_scene.go @@ -422,3 +422,63 @@ func changeMarker(ctx context.Context, changeType int, changedMarker models.Scen return sceneMarker, nil } + +func (r *mutationResolver) SceneIncrementO(ctx context.Context, id string) (int, error) { + sceneID, _ := strconv.Atoi(id) + + tx := database.DB.MustBeginTx(ctx, nil) + qb := models.NewSceneQueryBuilder() + + newVal, err := qb.IncrementOCounter(sceneID, tx) + if err != nil { + _ = tx.Rollback() + return 0, err + } + + // Commit + if err := tx.Commit(); err != nil { + return 0, err + } + + return newVal, nil +} + +func (r *mutationResolver) SceneDecrementO(ctx context.Context, id string) (int, error) { + sceneID, _ := strconv.Atoi(id) + + tx := database.DB.MustBeginTx(ctx, nil) + qb := models.NewSceneQueryBuilder() + + newVal, err := qb.DecrementOCounter(sceneID, tx) + if err != nil { + _ = tx.Rollback() + return 0, err + } + + // Commit + if err := tx.Commit(); err != nil { + return 0, err + } + + return newVal, nil +} + +func (r *mutationResolver) SceneResetO(ctx context.Context, id string) (int, error) { + sceneID, _ := strconv.Atoi(id) + + tx := database.DB.MustBeginTx(ctx, nil) + qb := models.NewSceneQueryBuilder() + + newVal, err := qb.ResetOCounter(sceneID, tx) + if err != nil { + _ = tx.Rollback() + return 0, err + } + + // Commit + if err := tx.Commit(); err != nil { + return 0, err + } + + return newVal, nil +} diff --git a/pkg/database/database.go b/pkg/database/database.go index 83c5c0fdc..66e0eba43 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -17,7 +17,7 @@ import ( ) var DB *sqlx.DB -var appSchemaVersion uint = 2 +var appSchemaVersion uint = 3 const sqlite3Driver = "sqlite3_regexp" diff --git a/pkg/database/migrations/3_o_counter.up.sql b/pkg/database/migrations/3_o_counter.up.sql new file mode 100644 index 000000000..fc860857a --- /dev/null +++ b/pkg/database/migrations/3_o_counter.up.sql @@ -0,0 +1 @@ +ALTER TABLE `scenes` ADD COLUMN `o_counter` tinyint not null default 0; diff --git a/pkg/models/model_scene.go b/pkg/models/model_scene.go index e025a95cc..e55f42117 100644 --- a/pkg/models/model_scene.go +++ b/pkg/models/model_scene.go @@ -15,6 +15,7 @@ type Scene struct { URL sql.NullString `db:"url" json:"url"` Date SQLiteDate `db:"date" json:"date"` Rating sql.NullInt64 `db:"rating" json:"rating"` + OCounter int `db:"o_counter" json:"o_counter"` Size sql.NullString `db:"size" json:"size"` Duration sql.NullFloat64 `db:"duration" json:"duration"` VideoCodec sql.NullString `db:"video_codec" json:"video_codec"` diff --git a/pkg/models/querybuilder_scene.go b/pkg/models/querybuilder_scene.go index 5cbb988ce..502af4bb5 100644 --- a/pkg/models/querybuilder_scene.go +++ b/pkg/models/querybuilder_scene.go @@ -76,6 +76,60 @@ func (qb *SceneQueryBuilder) Update(updatedScene ScenePartial, tx *sqlx.Tx) (*Sc return qb.find(updatedScene.ID, tx) } +func (qb *SceneQueryBuilder) IncrementOCounter(id int, tx *sqlx.Tx) (int, error) { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE scenes SET o_counter = o_counter + 1 WHERE scenes.id = ?`, + id, + ) + if err != nil { + return 0, err + } + + scene, err := qb.find(id, tx) + if err != nil { + return 0, err + } + + return scene.OCounter, nil +} + +func (qb *SceneQueryBuilder) DecrementOCounter(id int, tx *sqlx.Tx) (int, error) { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE scenes SET o_counter = o_counter - 1 WHERE scenes.id = ? and scenes.o_counter > 0`, + id, + ) + if err != nil { + return 0, err + } + + scene, err := qb.find(id, tx) + if err != nil { + return 0, err + } + + return scene.OCounter, nil +} + +func (qb *SceneQueryBuilder) ResetOCounter(id int, tx *sqlx.Tx) (int, error) { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE scenes SET o_counter = 0 WHERE scenes.id = ?`, + id, + ) + if err != nil { + return 0, err + } + + scene, err := qb.find(id, tx) + if err != nil { + return 0, err + } + + return scene.OCounter, nil +} + func (qb *SceneQueryBuilder) Destroy(id string, tx *sqlx.Tx) error { return executeDeleteQuery("scenes", id, tx) } @@ -178,6 +232,14 @@ func (qb *SceneQueryBuilder) Query(sceneFilter *SceneFilterType, findFilter *Fin } } + if oCounter := sceneFilter.OCounter; oCounter != nil { + clause, count := getIntCriterionWhereClause("scenes.o_counter", *sceneFilter.OCounter) + whereClauses = append(whereClauses, clause) + if count == 1 { + args = append(args, sceneFilter.OCounter.Value) + } + } + if durationFilter := sceneFilter.Duration; durationFilter != nil { clause, thisArgs := getDurationWhereClause(*durationFilter) whereClauses = append(whereClauses, clause) diff --git a/pkg/models/querybuilder_sql.go b/pkg/models/querybuilder_sql.go index 6e14bf965..e20645b7c 100644 --- a/pkg/models/querybuilder_sql.go +++ b/pkg/models/querybuilder_sql.go @@ -95,7 +95,7 @@ func getSort(sort string, direction string, tableName string) string { const randomSeedPrefix = "random_" - if strings.Contains(sort, "_count") { + if strings.HasSuffix(sort, "_count") { var relationTableName = strings.Split(sort, "_")[0] // TODO: pluralize? colName := getColumn(relationTableName, "id") return " ORDER BY COUNT(distinct " + colName + ") " + direction diff --git a/ui/v2/src/components/scenes/OCounterButton.tsx b/ui/v2/src/components/scenes/OCounterButton.tsx new file mode 100644 index 000000000..f7d9e14fd --- /dev/null +++ b/ui/v2/src/components/scenes/OCounterButton.tsx @@ -0,0 +1,50 @@ +import React, { FunctionComponent } from "react"; +import { Button, Popover, Menu, MenuItem } from "@blueprintjs/core"; +import { Icons } from "../../utils/icons"; + +export interface IOCounterButtonProps { + loading: boolean + value: number + onIncrement: () => void + onDecrement: () => void + onReset: () => void + onMenuOpened?: () => void + onMenuClosed?: () => void +} + +export const OCounterButton: FunctionComponent = (props: IOCounterButtonProps) => { + function renderButton() { + return ( +