From b288007037df17570eded18205a22925184ec3f9 Mon Sep 17 00:00:00 2001 From: com1234 Date: Sat, 10 Oct 2020 21:41:14 +0100 Subject: [PATCH 01/10] Add organized boolean to scene model (#729) --- graphql/documents/data/scene-slim.graphql | 1 + graphql/documents/data/scene.graphql | 1 + graphql/documents/mutations/scene.graphql | 4 ++++ graphql/schema/types/filters.graphql | 4 +++- graphql/schema/types/scene.graphql | 3 +++ pkg/api/resolver_model_scene.go | 7 +++++++ pkg/api/resolver_mutation_scene.go | 11 +++++++++++ pkg/database/database.go | 2 +- .../migrations/13_organized_scenes.up.sql | 1 + pkg/manager/jsonschema/scene.go | 1 + pkg/models/model_scene.go | 2 ++ pkg/models/querybuilder_scene.go | 14 ++++++++++++-- .../src/models/list-filter/criteria/criterion.ts | 3 +++ .../src/models/list-filter/criteria/organized.ts | 16 ++++++++++++++++ ui/v2.5/src/models/list-filter/criteria/utils.ts | 3 +++ ui/v2.5/src/models/list-filter/filter.ts | 10 ++++++++++ 16 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 pkg/database/migrations/13_organized_scenes.up.sql create mode 100644 ui/v2.5/src/models/list-filter/criteria/organized.ts diff --git a/graphql/documents/data/scene-slim.graphql b/graphql/documents/data/scene-slim.graphql index 535d79c87c5..1a1e691aff0 100644 --- a/graphql/documents/data/scene-slim.graphql +++ b/graphql/documents/data/scene-slim.graphql @@ -8,6 +8,7 @@ fragment SlimSceneData on Scene { date rating o_counter + organized path file { diff --git a/graphql/documents/data/scene.graphql b/graphql/documents/data/scene.graphql index aa02db41a34..8581b9c44f3 100644 --- a/graphql/documents/data/scene.graphql +++ b/graphql/documents/data/scene.graphql @@ -8,6 +8,7 @@ fragment SceneData on Scene { date rating o_counter + organized path file { diff --git a/graphql/documents/mutations/scene.graphql b/graphql/documents/mutations/scene.graphql index 0915b48cdec..dfee349af70 100644 --- a/graphql/documents/mutations/scene.graphql +++ b/graphql/documents/mutations/scene.graphql @@ -5,6 +5,7 @@ mutation SceneUpdate( $url: String, $date: String, $rating: Int, + $organized: Boolean, $studio_id: ID, $gallery_id: ID, $performer_ids: [ID!] = [], @@ -19,6 +20,7 @@ mutation SceneUpdate( url: $url, date: $date, rating: $rating, + organized: $organized, studio_id: $studio_id, gallery_id: $gallery_id, performer_ids: $performer_ids, @@ -37,6 +39,7 @@ mutation BulkSceneUpdate( $url: String, $date: String, $rating: Int, + $organized: Boolean, $studio_id: ID, $gallery_id: ID, $performer_ids: BulkUpdateIds, @@ -49,6 +52,7 @@ mutation BulkSceneUpdate( url: $url, date: $date, rating: $rating, + organized: $organized, studio_id: $studio_id, gallery_id: $gallery_id, performer_ids: $performer_ids, diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index cda7adb6d3d..633326b7453 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -66,6 +66,8 @@ input SceneMarkerFilterType { input SceneFilterType { """Filter by rating""" rating: IntCriterionInput + """Filter by organized""" + organized: Boolean """Filter by o-counter""" o_counter: IntCriterionInput """Filter by resolution""" @@ -153,4 +155,4 @@ input MultiCriterionInput { input GenderCriterionInput { value: GenderEnum modifier: CriterionModifier! -} \ No newline at end of file +} diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index 3844c6a8e5e..964681be584 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -32,6 +32,7 @@ type Scene { url: String date: String rating: Int + organized: Boolean! o_counter: Int path: String! @@ -59,6 +60,7 @@ input SceneUpdateInput { url: String date: String rating: Int + organized: Boolean studio_id: ID gallery_id: ID performer_ids: [ID!] @@ -87,6 +89,7 @@ input BulkSceneUpdateInput { url: String date: String rating: Int + organized: Boolean studio_id: ID gallery_id: ID performer_ids: BulkUpdateIds diff --git a/pkg/api/resolver_model_scene.go b/pkg/api/resolver_model_scene.go index 9d2c26e3bc9..7a0d57a63f0 100644 --- a/pkg/api/resolver_model_scene.go +++ b/pkg/api/resolver_model_scene.go @@ -36,6 +36,13 @@ func (r *sceneResolver) Details(ctx context.Context, obj *models.Scene) (*string return nil, nil } +func (r *sceneResolver) Organized(ctx context.Context, obj *models.Scene) (bool, error) { + if obj.Organized.Valid { + return obj.Organized.Bool, nil + } + return false, nil +} + func (r *sceneResolver) URL(ctx context.Context, obj *models.Scene) (*string, error) { if obj.URL.Valid { return &obj.URL.String, nil diff --git a/pkg/api/resolver_mutation_scene.go b/pkg/api/resolver_mutation_scene.go index 1e4d78b1e02..415eff7490c 100644 --- a/pkg/api/resolver_mutation_scene.go +++ b/pkg/api/resolver_mutation_scene.go @@ -82,6 +82,12 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T updatedScene.Date = &models.SQLiteDate{String: *input.Date, Valid: true} } + if input.Organized != nil { + updatedScene.Organized = sql.NullBool{Bool: *input.Organized, Valid: true} + } else { + updatedScene.Organized = sql.NullBool{Bool: false, Valid: true} + } + if input.CoverImage != nil && *input.CoverImage != "" { var err error _, coverImageData, err = utils.ProcessBase64Image(*input.CoverImage) @@ -231,6 +237,11 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul if input.Date != nil { updatedScene.Date = &models.SQLiteDate{String: *input.Date, Valid: true} } + if input.Organized != nil { + updatedScene.Organized = sql.NullBool{Bool: *input.Organized, Valid: true} + } else { + updatedScene.Organized = sql.NullBool{Bool: false, Valid: true} + } if input.Rating != nil { // a rating of 0 means unset the rating if *input.Rating == 0 { diff --git a/pkg/database/database.go b/pkg/database/database.go index e02aaca91c8..083cea314d6 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -19,7 +19,7 @@ import ( var DB *sqlx.DB var dbPath string -var appSchemaVersion uint = 12 +var appSchemaVersion uint = 13 var databaseSchemaVersion uint const sqlite3Driver = "sqlite3ex" diff --git a/pkg/database/migrations/13_organized_scenes.up.sql b/pkg/database/migrations/13_organized_scenes.up.sql new file mode 100644 index 00000000000..88bdbba8529 --- /dev/null +++ b/pkg/database/migrations/13_organized_scenes.up.sql @@ -0,0 +1 @@ +ALTER TABLE `scenes` ADD COLUMN `organized` boolean default 'FALSE'; diff --git a/pkg/manager/jsonschema/scene.go b/pkg/manager/jsonschema/scene.go index 8118a47a1dc..fb62792f418 100644 --- a/pkg/manager/jsonschema/scene.go +++ b/pkg/manager/jsonschema/scene.go @@ -42,6 +42,7 @@ type Scene struct { URL string `json:"url,omitempty"` Date string `json:"date,omitempty"` Rating int `json:"rating,omitempty"` + Organized bool `json:"organized,omitempty"` OCounter int `json:"o_counter,omitempty"` Details string `json:"details,omitempty"` Gallery string `json:"gallery,omitempty"` diff --git a/pkg/models/model_scene.go b/pkg/models/model_scene.go index a38d7960a01..3b75185fe5d 100644 --- a/pkg/models/model_scene.go +++ b/pkg/models/model_scene.go @@ -16,6 +16,7 @@ type Scene struct { URL sql.NullString `db:"url" json:"url"` Date SQLiteDate `db:"date" json:"date"` Rating sql.NullInt64 `db:"rating" json:"rating"` + Organized sql.NullBool `db:"organized" json:"organized"` OCounter int `db:"o_counter" json:"o_counter"` Size sql.NullString `db:"size" json:"size"` Duration sql.NullFloat64 `db:"duration" json:"duration"` @@ -43,6 +44,7 @@ type ScenePartial struct { URL *sql.NullString `db:"url" json:"url"` Date *SQLiteDate `db:"date" json:"date"` Rating *sql.NullInt64 `db:"rating" json:"rating"` + Organized sql.NullBool `db:"organized" json:"organized"` 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 4f18ab7a533..136c90a02a2 100644 --- a/pkg/models/querybuilder_scene.go +++ b/pkg/models/querybuilder_scene.go @@ -60,9 +60,9 @@ func NewSceneQueryBuilder() SceneQueryBuilder { func (qb *SceneQueryBuilder) Create(newScene Scene, tx *sqlx.Tx) (*Scene, error) { ensureTx(tx) result, err := tx.NamedExec( - `INSERT INTO scenes (oshash, checksum, path, title, details, url, date, rating, o_counter, size, duration, video_codec, + `INSERT INTO scenes (oshash, checksum, path, title, details, url, date, rating, organized, o_counter, size, duration, video_codec, audio_codec, format, width, height, framerate, bitrate, studio_id, created_at, updated_at) - VALUES (:oshash, :checksum, :path, :title, :details, :url, :date, :rating, :o_counter, :size, :duration, :video_codec, + VALUES (:oshash, :checksum, :path, :title, :details, :url, :date, :rating, :organized, :o_counter, :size, :duration, :video_codec, :audio_codec, :format, :width, :height, :framerate, :bitrate, :studio_id, :created_at, :updated_at) `, newScene, @@ -328,6 +328,16 @@ func (qb *SceneQueryBuilder) Query(sceneFilter *SceneFilterType, findFilter *Fin } } + if Organized := sceneFilter.Organized; Organized != nil { + var organized string + if *Organized == true { + organized = "1" + } else { + organized = "0" + } + query.addWhere("scenes.organized = " + organized) + } + if durationFilter := sceneFilter.Duration; durationFilter != nil { clause, thisArgs := getDurationWhereClause(*durationFilter) query.addWhere(clause) diff --git a/ui/v2.5/src/models/list-filter/criteria/criterion.ts b/ui/v2.5/src/models/list-filter/criteria/criterion.ts index cfc6af53221..f83f4ca0f34 100644 --- a/ui/v2.5/src/models/list-filter/criteria/criterion.ts +++ b/ui/v2.5/src/models/list-filter/criteria/criterion.ts @@ -7,6 +7,7 @@ import { ILabeledId, ILabeledValue, IOptionType } from "../types"; export type CriterionType = | "none" | "rating" + | "organized" | "o_counter" | "resolution" | "duration" @@ -50,6 +51,8 @@ export abstract class Criterion { return "None"; case "rating": return "Rating"; + case "organized": + return "Organized"; case "o_counter": return "O-Counter"; case "resolution": diff --git a/ui/v2.5/src/models/list-filter/criteria/organized.ts b/ui/v2.5/src/models/list-filter/criteria/organized.ts new file mode 100644 index 00000000000..287b8b94a01 --- /dev/null +++ b/ui/v2.5/src/models/list-filter/criteria/organized.ts @@ -0,0 +1,16 @@ +import { CriterionModifier } from "src/core/generated-graphql"; +import { Criterion, CriterionType, ICriterionOption } from "./criterion"; + +export class OrganizedCriterion extends Criterion { + public type: CriterionType = "organized"; + public parameterName: string = "organized"; + public modifier = CriterionModifier.Equals; + public modifierOptions = []; + public options: string[] = [true.toString(), false.toString()]; + public value: string = ""; +} + +export class OrganizedCriterionOption implements ICriterionOption { + public label: string = Criterion.getLabel("organized"); + public value: CriterionType = "organized"; +} diff --git a/ui/v2.5/src/models/list-filter/criteria/utils.ts b/ui/v2.5/src/models/list-filter/criteria/utils.ts index 13ef6d77472..5c571435fc5 100644 --- a/ui/v2.5/src/models/list-filter/criteria/utils.ts +++ b/ui/v2.5/src/models/list-filter/criteria/utils.ts @@ -7,6 +7,7 @@ import { NumberCriterion, DurationCriterion, } from "./criterion"; +import { OrganizedCriterion } from "./organized"; import { FavoriteCriterion } from "./favorite"; import { HasMarkersCriterion } from "./has-markers"; import { @@ -32,6 +33,8 @@ export function makeCriteria(type: CriterionType = "none") { return new NoneCriterion(); case "rating": return new RatingCriterion(); + case "organized": + return new OrganizedCriterion(); case "o_counter": case "scene_count": case "marker_count": diff --git a/ui/v2.5/src/models/list-filter/filter.ts b/ui/v2.5/src/models/list-filter/filter.ts index 237c2802e3b..64da8ceb3e9 100644 --- a/ui/v2.5/src/models/list-filter/filter.ts +++ b/ui/v2.5/src/models/list-filter/filter.ts @@ -25,6 +25,10 @@ import { FavoriteCriterion, FavoriteCriterionOption, } from "./criteria/favorite"; +import { + OrganizedCriterion, + OrganizedCriterionOption, +} from "./criteria/organized"; import { HasMarkersCriterion, HasMarkersCriterionOption, @@ -109,6 +113,7 @@ export class ListFilterModel { "title", "path", "rating", + "organized", "o_counter", "date", "filesize", @@ -125,6 +130,7 @@ export class ListFilterModel { this.criterionOptions = [ new NoneCriterionOption(), new RatingCriterionOption(), + new OrganizedCriterionOption(), ListFilterModel.createCriterionOption("o_counter"), new ResolutionCriterionOption(), ListFilterModel.createCriterionOption("duration"), @@ -388,6 +394,10 @@ export class ListFilterModel { }; break; } + case "organized": { + result.organized = (criterion as OrganizedCriterion).value === "true"; + break; + } case "o_counter": { const oCounterCrit = criterion as NumberCriterion; result.o_counter = { From efd36de6825878a1dc373e65840291c91c4de1bd Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 27 Nov 2020 12:17:56 +1100 Subject: [PATCH 02/10] Fix compile error --- pkg/api/resolver_mutation_scene.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/api/resolver_mutation_scene.go b/pkg/api/resolver_mutation_scene.go index ab10f44261d..54f481e5b27 100644 --- a/pkg/api/resolver_mutation_scene.go +++ b/pkg/api/resolver_mutation_scene.go @@ -83,9 +83,9 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T } if input.Organized != nil { - updatedScene.Organized = sql.NullBool{Bool: *input.Organized, Valid: true} + updatedScene.Organized = &sql.NullBool{Bool: *input.Organized, Valid: true} } else { - updatedScene.Organized = sql.NullBool{Bool: false, Valid: true} + updatedScene.Organized = &sql.NullBool{Bool: false, Valid: true} } if input.CoverImage != nil && *input.CoverImage != "" { @@ -253,9 +253,9 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul updatedScene.Date = &models.SQLiteDate{String: *input.Date, Valid: true} } if input.Organized != nil { - updatedScene.Organized = sql.NullBool{Bool: *input.Organized, Valid: true} + updatedScene.Organized = &sql.NullBool{Bool: *input.Organized, Valid: true} } else { - updatedScene.Organized = sql.NullBool{Bool: false, Valid: true} + updatedScene.Organized = &sql.NullBool{Bool: false, Valid: true} } if input.Rating != nil { // a rating of 0 means unset the rating From 2bcd7c677a67aaf75f0b5464f510e2aef3559f4c Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 27 Nov 2020 16:40:20 +1100 Subject: [PATCH 03/10] Add organized button to scene page --- .../Scenes/SceneDetails/OrganizedButton.tsx | 40 +++++++++++++++++++ .../components/Scenes/SceneDetails/Scene.tsx | 28 +++++++++++++ .../Scenes/SceneDetails/SceneEditPanel.tsx | 4 +- ui/v2.5/src/components/Scenes/styles.scss | 10 +++++ ui/v2.5/src/core/StashService.ts | 3 +- ui/v2.5/src/locale/en-GB.json | 1 + ui/v2.5/src/locale/en-US.json | 1 + 7 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 ui/v2.5/src/components/Scenes/SceneDetails/OrganizedButton.tsx diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/OrganizedButton.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/OrganizedButton.tsx new file mode 100644 index 00000000000..9c92fe3ffab --- /dev/null +++ b/ui/v2.5/src/components/Scenes/SceneDetails/OrganizedButton.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import cx from "classnames"; +import { Button, Spinner } from "react-bootstrap"; +import { Icon } from "src/components/Shared"; +import { defineMessages, useIntl } from "react-intl"; + +export interface IOrganizedButtonProps { + loading: boolean; + organized: boolean; + onClick: () => void; +} + +export const OrganizedButton: React.FC = ( + props: IOrganizedButtonProps +) => { + const intl = useIntl(); + const messages = defineMessages({ + organized: { + id: "organized", + defaultMessage: "Organized", + } + }); + + if (props.loading) return ; + + return ( + + ); +}; diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx index 6b8352ef37d..7321dc8c5b3 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx @@ -10,6 +10,7 @@ import { useSceneResetO, useSceneStreams, useSceneGenerateScreenshot, + useSceneUpdate } from "src/core/StashService"; import { GalleryViewer } from "src/components/Galleries/GalleryViewer"; import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; @@ -26,6 +27,7 @@ import { SceneMoviePanel } from "./SceneMoviePanel"; import { DeleteScenesDialog } from "../DeleteScenesDialog"; import { SceneGenerateDialog } from "../SceneGenerateDialog"; import { SceneVideoFilterPanel } from "./SceneVideoFilterPanel"; +import { OrganizedButton } from "./OrganizedButton"; interface ISceneParams { id?: string; @@ -36,6 +38,7 @@ export const Scene: React.FC = () => { const location = useLocation(); const history = useHistory(); const Toast = useToast(); + const [updateScene] = useSceneUpdate(); const [generateScreenshot] = useSceneGenerateScreenshot(); const [timestamp, setTimestamp] = useState(getInitialTimestamp()); const [collapsed, setCollapsed] = useState(false); @@ -52,6 +55,8 @@ export const Scene: React.FC = () => { const [decrementO] = useSceneDecrementO(scene?.id ?? "0"); const [resetO] = useSceneResetO(scene?.id ?? "0"); + const [organizedLoading, setOrganizedLoading] = useState(false); + const [activeTabKey, setActiveTabKey] = useState("scene-details-panel"); const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); @@ -69,6 +74,22 @@ export const Scene: React.FC = () => { ); } + const onOrganizedClick = async () => { + try { + setOrganizedLoading(true); + await updateScene({ + variables: { + id: scene?.id ?? "", + organized: !scene?.organized, + } + }); + } catch (e) { + Toast.error(e); + } finally { + setOrganizedLoading(false); + } + }; + const onIncrementClick = async () => { try { setOLoading(true); @@ -246,6 +267,13 @@ export const Scene: React.FC = () => { onReset={onResetClick} /> + + + {renderOperations()} diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index 0e6d326178a..d1fbb405b76 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -70,7 +70,7 @@ export const SceneEditPanel: React.FC = (props: IProps) => { // Network state const [isLoading, setIsLoading] = useState(true); - const [updateScene] = useSceneUpdate(getSceneInput()); + const [updateScene] = useSceneUpdate(); useEffect(() => { if (props.isVisible) { @@ -230,7 +230,7 @@ export const SceneEditPanel: React.FC = (props: IProps) => { async function onSave() { setIsLoading(true); try { - const result = await updateScene(); + const result = await updateScene({variables: getSceneInput()}); if (result.data?.sceneUpdate) { Toast.success({ content: "Updated scene" }); } diff --git a/ui/v2.5/src/components/Scenes/styles.scss b/ui/v2.5/src/components/Scenes/styles.scss index d4b23d7962e..4fe7a199e70 100644 --- a/ui/v2.5/src/components/Scenes/styles.scss +++ b/ui/v2.5/src/components/Scenes/styles.scss @@ -541,3 +541,13 @@ input[type="range"].blue-slider { } } } + +.organized-button { + &.not-organized { + color: rgba(191, 204, 214, 0.5); + } + + &.organized { + color: #ff7373; + } +} diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index 8bbc9e3ced4..769d1a531e3 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -323,9 +323,8 @@ const sceneMutationImpactedQueries = [ GQL.AllTagsDocument, ]; -export const useSceneUpdate = (input: GQL.SceneUpdateInput) => +export const useSceneUpdate = () => GQL.useSceneUpdateMutation({ - variables: input, update: deleteCache(sceneMutationImpactedQueries), }); diff --git a/ui/v2.5/src/locale/en-GB.json b/ui/v2.5/src/locale/en-GB.json index 2e2c7e7ae2c..698445cc4e6 100644 --- a/ui/v2.5/src/locale/en-GB.json +++ b/ui/v2.5/src/locale/en-GB.json @@ -6,6 +6,7 @@ "markers": "Markers", "movies": "Movies", "new": "New", + "organized": "Organised", "performers": "Performers", "scenes": "Scenes", "studios": "Studios", diff --git a/ui/v2.5/src/locale/en-US.json b/ui/v2.5/src/locale/en-US.json index a1a9e2742a6..fd6fc309896 100644 --- a/ui/v2.5/src/locale/en-US.json +++ b/ui/v2.5/src/locale/en-US.json @@ -6,6 +6,7 @@ "markers": "Markers", "movies": "Movies", "new": "New", + "organized": "Organized", "performers": "Performers", "scenes": "Scenes", "studios": "Studios", From 891383eb43112460fb9da0d83de09282bb29f035 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 4 Dec 2020 14:30:44 +1100 Subject: [PATCH 04/10] Update UI --- ui/v2.5/src/components/Scenes/SceneCard.tsx | 16 +++++++++++++++- .../src/components/Scenes/SceneDetails/Scene.tsx | 6 ++++-- .../Scenes/SceneDetails/SceneEditPanel.tsx | 6 +++++- ui/v2.5/src/components/Scenes/styles.scss | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/ui/v2.5/src/components/Scenes/SceneCard.tsx b/ui/v2.5/src/components/Scenes/SceneCard.tsx index bbcbe4f6a98..086fcb109f5 100644 --- a/ui/v2.5/src/components/Scenes/SceneCard.tsx +++ b/ui/v2.5/src/components/Scenes/SceneCard.tsx @@ -267,6 +267,18 @@ export const SceneCard: React.FC = ( } } + function maybeRenderOrganized() { + if (props.scene.organized) { + return ( +
+ +
+ ) + } + } + function maybeRenderPopoverButtonGroup() { if ( props.scene.tags.length > 0 || @@ -274,7 +286,8 @@ export const SceneCard: React.FC = ( props.scene.movies.length > 0 || props.scene.scene_markers.length > 0 || props.scene?.o_counter || - props.scene.gallery + props.scene.gallery || + props.scene.organized ) { return ( <> @@ -286,6 +299,7 @@ export const SceneCard: React.FC = ( {maybeRenderSceneMarkerPopoverButton()} {maybeRenderOCounter()} {maybeRenderGallery()} + {maybeRenderOrganized()} ); diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx index d245763a8a9..b6041d074d0 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx @@ -79,8 +79,10 @@ export const Scene: React.FC = () => { setOrganizedLoading(true); await updateScene({ variables: { - id: scene?.id ?? "", - organized: !scene?.organized, + input: { + id: scene?.id ?? "", + organized: !scene?.organized, + } } }); } catch (e) { diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index a7badb8de5a..98f0ff5d02b 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -230,7 +230,11 @@ export const SceneEditPanel: React.FC = (props: IProps) => { async function onSave() { setIsLoading(true); try { - const result = await updateScene({variables: getSceneInput()}); + const result = await updateScene({ + variables: { + input: getSceneInput(), + } + }); if (result.data?.sceneUpdate) { Toast.success({ content: "Updated scene" }); } diff --git a/ui/v2.5/src/components/Scenes/styles.scss b/ui/v2.5/src/components/Scenes/styles.scss index 1680ecb59bb..73538c968f0 100644 --- a/ui/v2.5/src/components/Scenes/styles.scss +++ b/ui/v2.5/src/components/Scenes/styles.scss @@ -542,6 +542,6 @@ input[type="range"].blue-slider { } &.organized { - color: #ff7373; + color: #664c3f; } } From cb5db40208575a5cf7d26ef26370e08a4687045b Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 4 Dec 2020 14:57:19 +1100 Subject: [PATCH 05/10] Set organized on multiple scenes --- .../components/Scenes/EditScenesDialog.tsx | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx b/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx index c3bc35360ea..c01f43299d9 100644 --- a/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx +++ b/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx @@ -28,12 +28,15 @@ export const EditScenesDialog: React.FC = ( GQL.BulkUpdateIdMode.Add ); const [tagIds, setTagIds] = useState(); + const [organized, setOrganized] = useState(); const [updateScenes] = useBulkSceneUpdate(getSceneInput()); // Network state const [isUpdating, setIsUpdating] = useState(false); + const checkboxRef = React.createRef(); + function makeBulkUpdateIds( ids: string[], mode: GQL.BulkUpdateIdMode @@ -119,6 +122,10 @@ export const EditScenesDialog: React.FC = ( sceneInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode); } + if (organized !== undefined) { + sceneInput.organized = organized; + } + return sceneInput; } @@ -217,6 +224,7 @@ export const EditScenesDialog: React.FC = ( let updateStudioID: string | undefined; let updatePerformerIds: string[] = []; let updateTagIds: string[] = []; + let updateOrganized: boolean | undefined; let first = true; state.forEach((scene: GQL.SlimSceneDataFragment) => { @@ -233,6 +241,7 @@ export const EditScenesDialog: React.FC = ( updatePerformerIds = scenePerformerIDs; updateTagIds = sceneTagIDs; first = false; + updateOrganized = scene.organized; } else { if (sceneRating !== updateRating) { updateRating = undefined; @@ -246,6 +255,9 @@ export const EditScenesDialog: React.FC = ( if (!_.isEqual(sceneTagIDs, updateTagIds)) { updateTagIds = []; } + if (scene.organized !== updateOrganized) { + updateOrganized = undefined; + } } }); @@ -258,8 +270,15 @@ export const EditScenesDialog: React.FC = ( if (tagMode === GQL.BulkUpdateIdMode.Set) { setTagIds(updateTagIds); } + setOrganized(updateOrganized); }, [props.selected, performerMode, tagMode]); + useEffect(() => { + if (checkboxRef.current) { + checkboxRef.current.indeterminate = organized === undefined; + } + }, [organized, checkboxRef]) + function renderMultiSelect( type: "performers" | "tags", ids: string[] | undefined @@ -305,6 +324,16 @@ export const EditScenesDialog: React.FC = ( ); } + function cycleOrganized() { + if (organized) { + setOrganized(undefined); + } else if (organized === undefined) { + setOrganized(false); + } else { + setOrganized(true); + } + } + function render() { return ( = ( Tags {renderMultiSelect("tags", tagIds)} + + + cycleOrganized()} + /> + ); From 1cc5ac619f14ae14c2352cc650c391a49c863ced Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Mon, 7 Dec 2020 13:12:50 +1100 Subject: [PATCH 06/10] Add flag to galleries and images --- graphql/documents/data/gallery-slim.graphql | 1 + graphql/documents/data/gallery.graphql | 1 + graphql/documents/data/image-slim.graphql | 1 + graphql/documents/data/image.graphql | 1 + graphql/documents/mutations/gallery.graphql | 1 + graphql/schema/types/filters.graphql | 6 ++- graphql/schema/types/gallery.graphql | 4 ++ graphql/schema/types/image.graphql | 3 ++ pkg/api/resolver_model_gallery.go | 7 ++++ pkg/api/resolver_model_image.go | 7 ++++ pkg/api/resolver_mutation_gallery.go | 2 + pkg/api/resolver_mutation_image.go | 2 + pkg/api/resolver_mutation_scene.go | 7 +--- .../migrations/16_organized_flag.up.sql | 3 ++ .../migrations/16_organized_scenes.up.sql | 1 - pkg/models/model_gallery.go | 2 + pkg/models/model_image.go | 2 + pkg/models/querybuilder_gallery.go | 14 ++++++- pkg/models/querybuilder_image.go | 14 ++++++- .../Galleries/EditGalleriesDialog.tsx | 42 ++++++++++++++++++- .../src/components/Galleries/GalleryCard.tsx | 16 ++++++- .../Galleries/GalleryDetails/Gallery.tsx | 36 +++++++++++++++- .../components/Images/EditImagesDialog.tsx | 41 +++++++++++++++++- ui/v2.5/src/components/Images/ImageCard.tsx | 16 ++++++- .../components/Images/ImageDetails/Image.tsx | 31 ++++++++++++++ .../components/Scenes/EditScenesDialog.tsx | 12 +++--- ui/v2.5/src/components/Scenes/SceneCard.tsx | 4 +- .../Scenes/SceneDetails/OrganizedButton.tsx | 4 +- .../components/Scenes/SceneDetails/Scene.tsx | 6 +-- .../Scenes/SceneDetails/SceneEditPanel.tsx | 2 +- ui/v2.5/src/models/list-filter/filter.ts | 10 +++++ 31 files changed, 267 insertions(+), 32 deletions(-) create mode 100644 pkg/database/migrations/16_organized_flag.up.sql delete mode 100644 pkg/database/migrations/16_organized_scenes.up.sql diff --git a/graphql/documents/data/gallery-slim.graphql b/graphql/documents/data/gallery-slim.graphql index 1f99cc201e1..8b73aa3db84 100644 --- a/graphql/documents/data/gallery-slim.graphql +++ b/graphql/documents/data/gallery-slim.graphql @@ -7,6 +7,7 @@ fragment GallerySlimData on Gallery { url details rating + organized image_count cover { ...SlimImageData diff --git a/graphql/documents/data/gallery.graphql b/graphql/documents/data/gallery.graphql index af99c77f7dd..188f08625a2 100644 --- a/graphql/documents/data/gallery.graphql +++ b/graphql/documents/data/gallery.graphql @@ -7,6 +7,7 @@ fragment GalleryData on Gallery { url details rating + organized images { ...SlimImageData } diff --git a/graphql/documents/data/image-slim.graphql b/graphql/documents/data/image-slim.graphql index 41789f5f5a3..ce80786ee0a 100644 --- a/graphql/documents/data/image-slim.graphql +++ b/graphql/documents/data/image-slim.graphql @@ -3,6 +3,7 @@ fragment SlimImageData on Image { checksum title rating + organized o_counter path diff --git a/graphql/documents/data/image.graphql b/graphql/documents/data/image.graphql index 9bd94b633b9..cf4d30e4113 100644 --- a/graphql/documents/data/image.graphql +++ b/graphql/documents/data/image.graphql @@ -3,6 +3,7 @@ fragment ImageData on Image { checksum title rating + organized o_counter path diff --git a/graphql/documents/mutations/gallery.graphql b/graphql/documents/mutations/gallery.graphql index 04565504e94..28e0ac5928c 100644 --- a/graphql/documents/mutations/gallery.graphql +++ b/graphql/documents/mutations/gallery.graphql @@ -4,6 +4,7 @@ mutation GalleryCreate( $url: String, $date: String, $rating: Int, + $organized: Boolean, $scene_id: ID, $studio_id: ID, $performer_ids: [ID!] = [], diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index eb41787b8f9..3af1310a115 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -70,7 +70,7 @@ input SceneFilterType { path: StringCriterionInput """Filter by rating""" rating: IntCriterionInput - """Filter by organized""" + """Filter by organized""" organized: Boolean """Filter by o-counter""" o_counter: IntCriterionInput @@ -119,6 +119,8 @@ input GalleryFilterType { is_zip: Boolean """Filter by rating""" rating: IntCriterionInput + """Filter by organized""" + organized: Boolean """Filter by average image resolution""" average_resolution: ResolutionEnum """Filter to only include scenes with this studio""" @@ -147,6 +149,8 @@ input ImageFilterType { path: StringCriterionInput """Filter by rating""" rating: IntCriterionInput + """Filter by organized""" + organized: Boolean """Filter by o-counter""" o_counter: IntCriterionInput """Filter by resolution""" diff --git a/graphql/schema/types/gallery.graphql b/graphql/schema/types/gallery.graphql index c39654a7e34..04fc538054a 100644 --- a/graphql/schema/types/gallery.graphql +++ b/graphql/schema/types/gallery.graphql @@ -8,6 +8,7 @@ type Gallery { date: String details: String rating: Int + organized: Boolean! scene: Scene studio: Studio image_count: Int! @@ -31,6 +32,7 @@ input GalleryCreateInput { date: String details: String rating: Int + organized: Boolean scene_id: ID studio_id: ID tag_ids: [ID!] @@ -45,6 +47,7 @@ input GalleryUpdateInput { date: String details: String rating: Int + organized: Boolean scene_id: ID studio_id: ID tag_ids: [ID!] @@ -58,6 +61,7 @@ input BulkGalleryUpdateInput { date: String details: String rating: Int + organized: Boolean scene_id: ID studio_id: ID tag_ids: BulkUpdateIds diff --git a/graphql/schema/types/image.graphql b/graphql/schema/types/image.graphql index efb90ffaf32..d3beab4394f 100644 --- a/graphql/schema/types/image.graphql +++ b/graphql/schema/types/image.graphql @@ -4,6 +4,7 @@ type Image { title: String rating: Int o_counter: Int + organized: Boolean! path: String! file: ImageFileType! # Resolver @@ -31,6 +32,7 @@ input ImageUpdateInput { id: ID! title: String rating: Int + organized: Boolean studio_id: ID performer_ids: [ID!] @@ -43,6 +45,7 @@ input BulkImageUpdateInput { ids: [ID!] title: String rating: Int + organized: Boolean studio_id: ID performer_ids: BulkUpdateIds diff --git a/pkg/api/resolver_model_gallery.go b/pkg/api/resolver_model_gallery.go index 0d73f82199d..e50cea03205 100644 --- a/pkg/api/resolver_model_gallery.go +++ b/pkg/api/resolver_model_gallery.go @@ -22,6 +22,13 @@ func (r *galleryResolver) Title(ctx context.Context, obj *models.Gallery) (*stri return nil, nil } +func (r *galleryResolver) Organized(ctx context.Context, obj *models.Gallery) (bool, error) { + if obj.Organized.Valid { + return obj.Organized.Bool, nil + } + return false, nil +} + func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) ([]*models.Image, error) { qb := models.NewImageQueryBuilder() diff --git a/pkg/api/resolver_model_image.go b/pkg/api/resolver_model_image.go index 6ca679f896d..d46d9ae25e8 100644 --- a/pkg/api/resolver_model_image.go +++ b/pkg/api/resolver_model_image.go @@ -21,6 +21,13 @@ func (r *imageResolver) Rating(ctx context.Context, obj *models.Image) (*int, er return nil, nil } +func (r *imageResolver) Organized(ctx context.Context, obj *models.Image) (bool, error) { + if obj.Organized.Valid { + return obj.Organized.Bool, nil + } + return false, nil +} + func (r *imageResolver) File(ctx context.Context, obj *models.Image) (*models.ImageFileType, error) { width := int(obj.Width.Int64) height := int(obj.Height.Int64) diff --git a/pkg/api/resolver_mutation_gallery.go b/pkg/api/resolver_mutation_gallery.go index c20bc4b38c0..5f1790d031c 100644 --- a/pkg/api/resolver_mutation_gallery.go +++ b/pkg/api/resolver_mutation_gallery.go @@ -205,6 +205,7 @@ func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, transl updatedGallery.Date = translator.sqliteDate(input.Date, "date") updatedGallery.Rating = translator.nullInt64(input.Rating, "rating") updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") + updatedGallery.Organized = translator.nullBool(input.Organized, "organized") // gallery scene is set from the scene only @@ -272,6 +273,7 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.B updatedGallery.Rating = translator.nullInt64(input.Rating, "rating") updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") updatedGallery.SceneID = translator.nullInt64FromString(input.SceneID, "scene_id") + updatedGallery.Organized = translator.nullBool(input.Organized, "organized") ret := []*models.Gallery{} diff --git a/pkg/api/resolver_mutation_image.go b/pkg/api/resolver_mutation_image.go index 7d621cbe96e..d67dc79b09e 100644 --- a/pkg/api/resolver_mutation_image.go +++ b/pkg/api/resolver_mutation_image.go @@ -77,6 +77,7 @@ func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, translator updatedImage.Title = translator.nullString(input.Title, "title") updatedImage.Rating = translator.nullInt64(input.Rating, "rating") updatedImage.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") + updatedImage.Organized = translator.nullBool(input.Organized, "organized") qb := models.NewImageQueryBuilder() jqb := models.NewJoinsQueryBuilder() @@ -142,6 +143,7 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.Bul updatedImage.Title = translator.nullString(input.Title, "title") updatedImage.Rating = translator.nullInt64(input.Rating, "rating") updatedImage.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") + updatedImage.Organized = translator.nullBool(input.Organized, "organized") ret := []*models.Image{} diff --git a/pkg/api/resolver_mutation_scene.go b/pkg/api/resolver_mutation_scene.go index 81f4fd9eb53..c229b1636fa 100644 --- a/pkg/api/resolver_mutation_scene.go +++ b/pkg/api/resolver_mutation_scene.go @@ -85,12 +85,7 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, translator updatedScene.Date = translator.sqliteDate(input.Date, "date") updatedScene.Rating = translator.nullInt64(input.Rating, "rating") updatedScene.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") - - if input.Organized != nil { - updatedScene.Organized = &sql.NullBool{Bool: *input.Organized, Valid: true} - } else { - updatedScene.Organized = &sql.NullBool{Bool: false, Valid: true} - } + updatedScene.Organized = translator.nullBool(input.Organized, "organized") if input.CoverImage != nil && *input.CoverImage != "" { var err error diff --git a/pkg/database/migrations/16_organized_flag.up.sql b/pkg/database/migrations/16_organized_flag.up.sql new file mode 100644 index 00000000000..178fd0b546c --- /dev/null +++ b/pkg/database/migrations/16_organized_flag.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE `scenes` ADD COLUMN `organized` boolean default 'FALSE'; +ALTER TABLE `images` ADD COLUMN `organized` boolean default 'FALSE'; +ALTER TABLE `galleries` ADD COLUMN `organized` boolean default 'FALSE'; diff --git a/pkg/database/migrations/16_organized_scenes.up.sql b/pkg/database/migrations/16_organized_scenes.up.sql deleted file mode 100644 index 88bdbba8529..00000000000 --- a/pkg/database/migrations/16_organized_scenes.up.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `scenes` ADD COLUMN `organized` boolean default 'FALSE'; diff --git a/pkg/models/model_gallery.go b/pkg/models/model_gallery.go index ed8809f7922..c5d0678f6dc 100644 --- a/pkg/models/model_gallery.go +++ b/pkg/models/model_gallery.go @@ -14,6 +14,7 @@ type Gallery struct { Date SQLiteDate `db:"date" json:"date"` Details sql.NullString `db:"details" json:"details"` Rating sql.NullInt64 `db:"rating" json:"rating"` + Organized sql.NullBool `db:"organized" json:"organized"` StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` SceneID sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"` FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` @@ -32,6 +33,7 @@ type GalleryPartial struct { Date *SQLiteDate `db:"date" json:"date"` Details *sql.NullString `db:"details" json:"details"` Rating *sql.NullInt64 `db:"rating" json:"rating"` + Organized *sql.NullBool `db:"organized" json:"organized"` StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` SceneID *sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"` FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` diff --git a/pkg/models/model_image.go b/pkg/models/model_image.go index 0da92c00fdc..8cc07c4e9c0 100644 --- a/pkg/models/model_image.go +++ b/pkg/models/model_image.go @@ -11,6 +11,7 @@ type Image struct { Path string `db:"path" json:"path"` Title sql.NullString `db:"title" json:"title"` Rating sql.NullInt64 `db:"rating" json:"rating"` + Organized sql.NullBool `db:"organized" json:"organized"` OCounter int `db:"o_counter" json:"o_counter"` Size sql.NullInt64 `db:"size" json:"size"` Width sql.NullInt64 `db:"width" json:"width"` @@ -29,6 +30,7 @@ type ImagePartial struct { Path *string `db:"path" json:"path"` Title *sql.NullString `db:"title" json:"title"` Rating *sql.NullInt64 `db:"rating" json:"rating"` + Organized *sql.NullBool `db:"organized" json:"organized"` Size *sql.NullInt64 `db:"size" json:"size"` Width *sql.NullInt64 `db:"width" json:"width"` Height *sql.NullInt64 `db:"height" json:"height"` diff --git a/pkg/models/querybuilder_gallery.go b/pkg/models/querybuilder_gallery.go index 6166bc73a4a..23c40138dad 100644 --- a/pkg/models/querybuilder_gallery.go +++ b/pkg/models/querybuilder_gallery.go @@ -21,8 +21,8 @@ func NewGalleryQueryBuilder() GalleryQueryBuilder { func (qb *GalleryQueryBuilder) Create(newGallery Gallery, tx *sqlx.Tx) (*Gallery, error) { ensureTx(tx) result, err := tx.NamedExec( - `INSERT INTO galleries (path, checksum, zip, title, date, details, url, studio_id, rating, scene_id, file_mod_time, created_at, updated_at) - VALUES (:path, :checksum, :zip, :title, :date, :details, :url, :studio_id, :rating, :scene_id, :file_mod_time, :created_at, :updated_at) + `INSERT INTO galleries (path, checksum, zip, title, date, details, url, studio_id, rating, organized, scene_id, file_mod_time, created_at, updated_at) + VALUES (:path, :checksum, :zip, :title, :date, :details, :url, :studio_id, :rating, :organized, :scene_id, :file_mod_time, :created_at, :updated_at) `, newGallery, ) @@ -232,6 +232,16 @@ func (qb *GalleryQueryBuilder) Query(galleryFilter *GalleryFilterType, findFilte query.handleIntCriterionInput(galleryFilter.Rating, "galleries.rating") qb.handleAverageResolutionFilter(&query, galleryFilter.AverageResolution) + if Organized := galleryFilter.Organized; Organized != nil { + var organized string + if *Organized == true { + organized = "1" + } else { + organized = "0" + } + query.addWhere("galleries.organized = " + organized) + } + if isMissingFilter := galleryFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" { switch *isMissingFilter { case "scene": diff --git a/pkg/models/querybuilder_image.go b/pkg/models/querybuilder_image.go index 6441db5d0da..e7163659a68 100644 --- a/pkg/models/querybuilder_image.go +++ b/pkg/models/querybuilder_image.go @@ -61,9 +61,9 @@ func NewImageQueryBuilder() ImageQueryBuilder { func (qb *ImageQueryBuilder) Create(newImage Image, tx *sqlx.Tx) (*Image, error) { ensureTx(tx) result, err := tx.NamedExec( - `INSERT INTO images (checksum, path, title, rating, o_counter, size, + `INSERT INTO images (checksum, path, title, rating, organized, o_counter, size, width, height, studio_id, file_mod_time, created_at, updated_at) - VALUES (:checksum, :path, :title, :rating, :o_counter, :size, + VALUES (:checksum, :path, :title, :rating, :organized, :o_counter, :size, :width, :height, :studio_id, :file_mod_time, :created_at, :updated_at) `, newImage, @@ -309,6 +309,16 @@ func (qb *ImageQueryBuilder) Query(imageFilter *ImageFilterType, findFilter *Fin } } + if Organized := imageFilter.Organized; Organized != nil { + var organized string + if *Organized == true { + organized = "1" + } else { + organized = "0" + } + query.addWhere("images.organized = " + organized) + } + if resolutionFilter := imageFilter.Resolution; resolutionFilter != nil { if resolution := resolutionFilter.String(); resolutionFilter.IsValid() { switch resolution { diff --git a/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx b/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx index 39973fc65c8..c61acd5ff2b 100644 --- a/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx +++ b/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx @@ -28,12 +28,15 @@ export const EditGalleriesDialog: React.FC = ( GQL.BulkUpdateIdMode.Add ); const [tagIds, setTagIds] = useState(); + const [organized, setOrganized] = useState(); const [updateGalleries] = useBulkGalleryUpdate(); // Network state const [isUpdating, setIsUpdating] = useState(false); + const checkboxRef = React.createRef(); + function makeBulkUpdateIds( ids: string[], mode: GQL.BulkUpdateIdMode @@ -119,6 +122,10 @@ export const EditGalleriesDialog: React.FC = ( galleryInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode); } + if (organized !== undefined) { + galleryInput.organized = organized; + } + return galleryInput; } @@ -223,6 +230,7 @@ export const EditGalleriesDialog: React.FC = ( let updateStudioID: string | undefined; let updatePerformerIds: string[] = []; let updateTagIds: string[] = []; + let updateOrganized: boolean | undefined; let first = true; state.forEach((gallery: GQL.GallerySlimDataFragment) => { @@ -238,6 +246,7 @@ export const EditGalleriesDialog: React.FC = ( updateStudioID = GalleriestudioID; updatePerformerIds = galleryPerformerIDs; updateTagIds = galleryTagIDs; + updateOrganized = gallery.organized; first = false; } else { if (galleryRating !== updateRating) { @@ -252,6 +261,9 @@ export const EditGalleriesDialog: React.FC = ( if (!_.isEqual(galleryTagIDs, updateTagIds)) { updateTagIds = []; } + if (gallery.organized !== updateOrganized) { + updateOrganized = undefined; + } } }); @@ -264,8 +276,16 @@ export const EditGalleriesDialog: React.FC = ( if (tagMode === GQL.BulkUpdateIdMode.Set) { setTagIds(updateTagIds); } + + setOrganized(updateOrganized); }, [props.selected, performerMode, tagMode]); + useEffect(() => { + if (checkboxRef.current) { + checkboxRef.current.indeterminate = organized === undefined; + } + }, [organized, checkboxRef]); + function renderMultiSelect( type: "performers" | "tags", ids: string[] | undefined @@ -311,6 +331,16 @@ export const EditGalleriesDialog: React.FC = ( ); } + function cycleOrganized() { + if (organized) { + setOrganized(undefined); + } else if (organized === undefined) { + setOrganized(false); + } else { + setOrganized(true); + } + } + function render() { return ( = ( {renderMultiSelect("performers", performerIds)} - + Tags {renderMultiSelect("tags", tagIds)} + + + cycleOrganized()} + /> + ); diff --git a/ui/v2.5/src/components/Galleries/GalleryCard.tsx b/ui/v2.5/src/components/Galleries/GalleryCard.tsx index c73f5f9fb9d..af7e42aec86 100644 --- a/ui/v2.5/src/components/Galleries/GalleryCard.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryCard.tsx @@ -104,11 +104,24 @@ export const GalleryCard: React.FC = (props) => { ); } + function maybeRenderOrganized() { + if (props.gallery.organized) { + return ( +
+ +
+ ); + } + } + function maybeRenderPopoverButtonGroup() { if ( props.gallery.scene || props.gallery.performers.length > 0 || - props.gallery.tags.length > 0 + props.gallery.tags.length > 0 || + props.gallery.organized ) { return ( <> @@ -117,6 +130,7 @@ export const GalleryCard: React.FC = (props) => { {maybeRenderTagPopoverButton()} {maybeRenderPerformerPopoverButton()} {maybeRenderScenePopoverButton()} + {maybeRenderOrganized()} ); diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx index 6b6100f8e4a..9bd621dc944 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx @@ -1,10 +1,12 @@ import { Tab, Nav, Dropdown } from "react-bootstrap"; import React, { useEffect, useState } from "react"; import { useParams, useHistory, Link } from "react-router-dom"; -import { useFindGallery } from "src/core/StashService"; +import { useFindGallery, useGalleryUpdate } from "src/core/StashService"; import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; import { TextUtils } from "src/utils"; import * as Mousetrap from "mousetrap"; +import { useToast } from "src/hooks"; +import { OrganizedButton } from "src/components/Scenes/SceneDetails/OrganizedButton"; import { GalleryEditPanel } from "./GalleryEditPanel"; import { GalleryDetailPanel } from "./GalleryDetailPanel"; import { DeleteGalleriesDialog } from "../DeleteGalleriesDialog"; @@ -20,6 +22,7 @@ interface IGalleryParams { export const Gallery: React.FC = () => { const { tab = "images", id = "new" } = useParams(); const history = useHistory(); + const Toast = useToast(); const isNew = id === "new"; const { data, error, loading } = useFindGallery(id); @@ -34,6 +37,28 @@ export const Gallery: React.FC = () => { } }; + const [updateGallery] = useGalleryUpdate(); + + const [organizedLoading, setOrganizedLoading] = useState(false); + + const onOrganizedClick = async () => { + try { + setOrganizedLoading(true); + await updateGallery({ + variables: { + input: { + id: gallery?.id ?? "", + organized: !gallery?.organized, + }, + }, + }); + } catch (e) { + Toast.error(e); + } finally { + setOrganizedLoading(false); + } + }; + const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); function onDeleteDialogClosed(deleted: boolean) { @@ -103,7 +128,14 @@ export const Gallery: React.FC = () => { Edit - {renderOperations()} + + + + {renderOperations()} diff --git a/ui/v2.5/src/components/Images/EditImagesDialog.tsx b/ui/v2.5/src/components/Images/EditImagesDialog.tsx index 41776b14391..4c542d35a32 100644 --- a/ui/v2.5/src/components/Images/EditImagesDialog.tsx +++ b/ui/v2.5/src/components/Images/EditImagesDialog.tsx @@ -28,12 +28,15 @@ export const EditImagesDialog: React.FC = ( GQL.BulkUpdateIdMode.Add ); const [tagIds, setTagIds] = useState(); + const [organized, setOrganized] = useState(); const [updateImages] = useBulkImageUpdate(); // Network state const [isUpdating, setIsUpdating] = useState(false); + const checkboxRef = React.createRef(); + function makeBulkUpdateIds( ids: string[], mode: GQL.BulkUpdateIdMode @@ -119,6 +122,10 @@ export const EditImagesDialog: React.FC = ( imageInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode); } + if (organized !== undefined) { + imageInput.organized = organized; + } + return imageInput; } @@ -221,6 +228,7 @@ export const EditImagesDialog: React.FC = ( let updateStudioID: string | undefined; let updatePerformerIds: string[] = []; let updateTagIds: string[] = []; + let updateOrganized: boolean | undefined; let first = true; state.forEach((image: GQL.SlimImageDataFragment) => { @@ -236,6 +244,7 @@ export const EditImagesDialog: React.FC = ( updateStudioID = imageStudioID; updatePerformerIds = imagePerformerIDs; updateTagIds = imageTagIDs; + updateOrganized = image.organized; first = false; } else { if (imageRating !== updateRating) { @@ -250,6 +259,9 @@ export const EditImagesDialog: React.FC = ( if (!_.isEqual(imageTagIDs, updateTagIds)) { updateTagIds = []; } + if (image.organized !== updateOrganized) { + updateOrganized = undefined; + } } }); @@ -262,8 +274,15 @@ export const EditImagesDialog: React.FC = ( if (tagMode === GQL.BulkUpdateIdMode.Set) { setTagIds(updateTagIds); } + setOrganized(updateOrganized); }, [props.selected, performerMode, tagMode]); + useEffect(() => { + if (checkboxRef.current) { + checkboxRef.current.indeterminate = organized === undefined; + } + }, [organized, checkboxRef]); + function renderMultiSelect( type: "performers" | "tags", ids: string[] | undefined @@ -309,6 +328,16 @@ export const EditImagesDialog: React.FC = ( ); } + function cycleOrganized() { + if (organized) { + setOrganized(undefined); + } else if (organized === undefined) { + setOrganized(false); + } else { + setOrganized(true); + } + } + function render() { return ( = ( {renderMultiSelect("performers", performerIds)} - + Tags {renderMultiSelect("tags", tagIds)} + + + cycleOrganized()} + /> + ); diff --git a/ui/v2.5/src/components/Images/ImageCard.tsx b/ui/v2.5/src/components/Images/ImageCard.tsx index 71e38fccab0..d40dda346dc 100644 --- a/ui/v2.5/src/components/Images/ImageCard.tsx +++ b/ui/v2.5/src/components/Images/ImageCard.tsx @@ -93,11 +93,24 @@ export const ImageCard: React.FC = ( } } + function maybeRenderOrganized() { + if (props.image.organized) { + return ( +
+ +
+ ); + } + } + function maybeRenderPopoverButtonGroup() { if ( props.image.tags.length > 0 || props.image.performers.length > 0 || - props.image?.o_counter + props.image.o_counter || + props.image.organized ) { return ( <> @@ -106,6 +119,7 @@ export const ImageCard: React.FC = ( {maybeRenderTagPopoverButton()} {maybeRenderPerformerPopoverButton()} {maybeRenderOCounter()} + {maybeRenderOrganized()} ); diff --git a/ui/v2.5/src/components/Images/ImageDetails/Image.tsx b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx index f61f2bbba67..413c17b7606 100644 --- a/ui/v2.5/src/components/Images/ImageDetails/Image.tsx +++ b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx @@ -6,12 +6,14 @@ import { useImageIncrementO, useImageDecrementO, useImageResetO, + useImageUpdate, } from "src/core/StashService"; import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; import { useToast } from "src/hooks"; import { TextUtils } from "src/utils"; import * as Mousetrap from "mousetrap"; import { OCounterButton } from "src/components/Scenes/SceneDetails/OCounterButton"; +import { OrganizedButton } from "src/components/Scenes/SceneDetails/OrganizedButton"; import { ImageFileInfoPanel } from "./ImageFileInfoPanel"; import { ImageEditPanel } from "./ImageEditPanel"; import { ImageDetailPanel } from "./ImageDetailPanel"; @@ -33,10 +35,32 @@ export const Image: React.FC = () => { const [decrementO] = useImageDecrementO(image?.id ?? "0"); const [resetO] = useImageResetO(image?.id ?? "0"); + const [updateImage] = useImageUpdate(); + + const [organizedLoading, setOrganizedLoading] = useState(false); + const [activeTabKey, setActiveTabKey] = useState("image-details-panel"); const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); + const onOrganizedClick = async () => { + try { + setOrganizedLoading(true); + await updateImage({ + variables: { + input: { + id: image?.id ?? "", + organized: !image?.organized, + }, + }, + }); + } catch (e) { + Toast.error(e); + } finally { + setOrganizedLoading(false); + } + }; + const onIncrementClick = async () => { try { setOLoading(true); @@ -139,6 +163,13 @@ export const Image: React.FC = () => { onReset={onResetClick} /> + + + {renderOperations()} diff --git a/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx b/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx index c01f43299d9..3ab34f1f23f 100644 --- a/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx +++ b/ui/v2.5/src/components/Scenes/EditScenesDialog.tsx @@ -277,7 +277,7 @@ export const EditScenesDialog: React.FC = ( if (checkboxRef.current) { checkboxRef.current.indeterminate = organized === undefined; } - }, [organized, checkboxRef]) + }, [organized, checkboxRef]); function renderMultiSelect( type: "performers" | "tags", @@ -382,16 +382,16 @@ export const EditScenesDialog: React.FC = ( {renderMultiSelect("performers", performerIds)} - + Tags {renderMultiSelect("tags", tagIds)} - cycleOrganized()} /> diff --git a/ui/v2.5/src/components/Scenes/SceneCard.tsx b/ui/v2.5/src/components/Scenes/SceneCard.tsx index 086fcb109f5..495eef3c3c6 100644 --- a/ui/v2.5/src/components/Scenes/SceneCard.tsx +++ b/ui/v2.5/src/components/Scenes/SceneCard.tsx @@ -275,7 +275,7 @@ export const SceneCard: React.FC = ( - ) + ); } } @@ -286,7 +286,7 @@ export const SceneCard: React.FC = ( props.scene.movies.length > 0 || props.scene.scene_markers.length > 0 || props.scene?.o_counter || - props.scene.gallery || + props.scene.gallery || props.scene.organized ) { return ( diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/OrganizedButton.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/OrganizedButton.tsx index 9c92fe3ffab..84a4abae217 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/OrganizedButton.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/OrganizedButton.tsx @@ -18,9 +18,9 @@ export const OrganizedButton: React.FC = ( organized: { id: "organized", defaultMessage: "Organized", - } + }, }); - + if (props.loading) return ; return ( diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx index b6041d074d0..4f1ca2d66d3 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx @@ -10,7 +10,7 @@ import { useSceneResetO, useSceneStreams, useSceneGenerateScreenshot, - useSceneUpdate + useSceneUpdate, } from "src/core/StashService"; import { GalleryViewer } from "src/components/Galleries/GalleryViewer"; import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; @@ -82,8 +82,8 @@ export const Scene: React.FC = () => { input: { id: scene?.id ?? "", organized: !scene?.organized, - } - } + }, + }, }); } catch (e) { Toast.error(e); diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index 98f0ff5d02b..00ee96dba24 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -233,7 +233,7 @@ export const SceneEditPanel: React.FC = (props: IProps) => { const result = await updateScene({ variables: { input: getSceneInput(), - } + }, }); if (result.data?.sceneUpdate) { Toast.success({ content: "Updated scene" }); diff --git a/ui/v2.5/src/models/list-filter/filter.ts b/ui/v2.5/src/models/list-filter/filter.ts index 1c77605906e..ae58e37b88f 100644 --- a/ui/v2.5/src/models/list-filter/filter.ts +++ b/ui/v2.5/src/models/list-filter/filter.ts @@ -167,6 +167,7 @@ export class ListFilterModel { new NoneCriterionOption(), ListFilterModel.createCriterionOption("path"), new RatingCriterionOption(), + new OrganizedCriterionOption(), ListFilterModel.createCriterionOption("o_counter"), new ResolutionCriterionOption(), new ImageIsMissingCriterionOption(), @@ -240,6 +241,7 @@ export class ListFilterModel { new NoneCriterionOption(), ListFilterModel.createCriterionOption("path"), new RatingCriterionOption(), + new OrganizedCriterionOption(), new AverageResolutionCriterionOption(), new GalleryIsMissingCriterionOption(), new TagsCriterionOption(), @@ -679,6 +681,10 @@ export class ListFilterModel { }; break; } + case "organized": { + result.organized = (criterion as OrganizedCriterion).value === "true"; + break; + } case "o_counter": { const oCounterCrit = criterion as NumberCriterion; result.o_counter = { @@ -810,6 +816,10 @@ export class ListFilterModel { }; break; } + case "organized": { + result.organized = (criterion as OrganizedCriterion).value === "true"; + break; + } case "average_resolution": { switch ((criterion as AverageResolutionCriterion).value) { case "240p": From dadf04a64a69fdfcb401b6ceeb2699913aa4ab5c Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Mon, 7 Dec 2020 13:42:24 +1100 Subject: [PATCH 07/10] Import/export changes --- pkg/gallery/export.go | 4 +++ pkg/gallery/export_test.go | 41 +++++++++++++++++-------------- pkg/gallery/import.go | 5 ++++ pkg/gallery/import_test.go | 22 +++++++++-------- pkg/image/export.go | 4 +++ pkg/image/export_test.go | 41 +++++++++++++++++-------------- pkg/image/import.go | 4 +++ pkg/manager/jsonschema/gallery.go | 1 + pkg/manager/jsonschema/image.go | 1 + pkg/models/modelstest/sql.go | 7 ++++++ pkg/scene/export.go | 4 +++ pkg/scene/export_test.go | 19 ++++++++------ pkg/scene/import.go | 5 ++++ 13 files changed, 102 insertions(+), 56 deletions(-) diff --git a/pkg/gallery/export.go b/pkg/gallery/export.go index fd6999081b6..a77ade5a119 100644 --- a/pkg/gallery/export.go +++ b/pkg/gallery/export.go @@ -40,6 +40,10 @@ func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) { newGalleryJSON.Rating = int(gallery.Rating.Int64) } + if gallery.Organized.Valid { + newGalleryJSON.Organized = gallery.Organized.Bool + } + if gallery.Details.Valid { newGalleryJSON.Details = gallery.Details.String } diff --git a/pkg/gallery/export_test.go b/pkg/gallery/export_test.go index 439a116de0e..734e93db2cc 100644 --- a/pkg/gallery/export_test.go +++ b/pkg/gallery/export_test.go @@ -25,14 +25,15 @@ const ( ) const ( - path = "path" - zip = true - url = "url" - checksum = "checksum" - title = "title" - date = "2001-01-01" - rating = 5 - details = "details" + path = "path" + zip = true + url = "url" + checksum = "checksum" + title = "title" + date = "2001-01-01" + rating = 5 + organized = true + details = "details" ) const ( @@ -58,9 +59,10 @@ func createFullGallery(id int) models.Gallery { String: date, Valid: true, }, - Details: modelstest.NullString(details), - Rating: modelstest.NullInt64(rating), - URL: modelstest.NullString(url), + Details: modelstest.NullString(details), + Rating: modelstest.NullInt64(rating), + Organized: modelstest.NullBool(organized), + URL: modelstest.NullString(url), CreatedAt: models.SQLiteTimestamp{ Timestamp: createTime, }, @@ -84,14 +86,15 @@ func createEmptyGallery(id int) models.Gallery { func createFullJSONGallery() *jsonschema.Gallery { return &jsonschema.Gallery{ - Title: title, - Path: path, - Zip: zip, - Checksum: checksum, - Date: date, - Details: details, - Rating: rating, - URL: url, + Title: title, + Path: path, + Zip: zip, + Checksum: checksum, + Date: date, + Details: details, + Rating: rating, + Organized: organized, + URL: url, CreatedAt: models.JSONTime{ Time: createTime, }, diff --git a/pkg/gallery/import.go b/pkg/gallery/import.go index 3643a234627..c77d494e158 100644 --- a/pkg/gallery/import.go +++ b/pkg/gallery/import.go @@ -68,6 +68,11 @@ func (i *Importer) galleryJSONToGallery(galleryJSON jsonschema.Gallery) models.G newGallery.Rating = sql.NullInt64{Int64: int64(galleryJSON.Rating), Valid: true} } + newGallery.Organized = sql.NullBool{ + Bool: galleryJSON.Organized, + Valid: true, + } + newGallery.CreatedAt = models.SQLiteTimestamp{Timestamp: galleryJSON.CreatedAt.GetTime()} newGallery.UpdatedAt = models.SQLiteTimestamp{Timestamp: galleryJSON.UpdatedAt.GetTime()} diff --git a/pkg/gallery/import_test.go b/pkg/gallery/import_test.go index 6cbcbc32a38..ce846f6c783 100644 --- a/pkg/gallery/import_test.go +++ b/pkg/gallery/import_test.go @@ -56,13 +56,14 @@ func TestImporterName(t *testing.T) { func TestImporterPreImport(t *testing.T) { i := Importer{ Input: jsonschema.Gallery{ - Path: path, - Checksum: checksum, - Title: title, - Date: date, - Details: details, - Rating: rating, - URL: url, + Path: path, + Checksum: checksum, + Title: title, + Date: date, + Details: details, + Rating: rating, + Organized: organized, + URL: url, CreatedAt: models.JSONTime{ Time: createdAt, }, @@ -83,9 +84,10 @@ func TestImporterPreImport(t *testing.T) { String: date, Valid: true, }, - Details: modelstest.NullString(details), - Rating: modelstest.NullInt64(rating), - URL: modelstest.NullString(url), + Details: modelstest.NullString(details), + Rating: modelstest.NullInt64(rating), + Organized: modelstest.NullBool(organized), + URL: modelstest.NullString(url), CreatedAt: models.SQLiteTimestamp{ Timestamp: createdAt, }, diff --git a/pkg/image/export.go b/pkg/image/export.go index f75da183260..35d2e8c3b0e 100644 --- a/pkg/image/export.go +++ b/pkg/image/export.go @@ -23,6 +23,10 @@ func ToBasicJSON(image *models.Image) *jsonschema.Image { newImageJSON.Rating = int(image.Rating.Int64) } + if image.Organized.Valid { + newImageJSON.Organized = image.Organized.Bool + } + newImageJSON.OCounter = image.OCounter newImageJSON.File = getImageFileJSON(image) diff --git a/pkg/image/export_test.go b/pkg/image/export_test.go index 8bbc198b06c..7c07fa15225 100644 --- a/pkg/image/export_test.go +++ b/pkg/image/export_test.go @@ -39,13 +39,14 @@ const ( ) const ( - checksum = "checksum" - title = "title" - rating = 5 - ocounter = 2 - size = 123 - width = 100 - height = 100 + checksum = "checksum" + title = "title" + rating = 5 + organized = true + ocounter = 2 + size = 123 + width = 100 + height = 100 ) const ( @@ -63,14 +64,15 @@ var updateTime time.Time = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) func createFullImage(id int) models.Image { return models.Image{ - ID: id, - Title: modelstest.NullString(title), - Checksum: checksum, - Height: modelstest.NullInt64(height), - OCounter: ocounter, - Rating: modelstest.NullInt64(rating), - Size: modelstest.NullInt64(int64(size)), - Width: modelstest.NullInt64(width), + ID: id, + Title: modelstest.NullString(title), + Checksum: checksum, + Height: modelstest.NullInt64(height), + OCounter: ocounter, + Rating: modelstest.NullInt64(rating), + Organized: modelstest.NullBool(organized), + Size: modelstest.NullInt64(int64(size)), + Width: modelstest.NullInt64(width), CreatedAt: models.SQLiteTimestamp{ Timestamp: createTime, }, @@ -94,10 +96,11 @@ func createEmptyImage(id int) models.Image { func createFullJSONImage() *jsonschema.Image { return &jsonschema.Image{ - Title: title, - Checksum: checksum, - OCounter: ocounter, - Rating: rating, + Title: title, + Checksum: checksum, + OCounter: ocounter, + Rating: rating, + Organized: organized, File: &jsonschema.ImageFile{ Height: height, Size: size, diff --git a/pkg/image/import.go b/pkg/image/import.go index b970be83d7c..0ab3e105293 100644 --- a/pkg/image/import.go +++ b/pkg/image/import.go @@ -63,6 +63,10 @@ func (i *Importer) imageJSONToImage(imageJSON jsonschema.Image) models.Image { newImage.Rating = sql.NullInt64{Int64: int64(imageJSON.Rating), Valid: true} } + newImage.Organized = sql.NullBool{ + Bool: imageJSON.Organized, + } + newImage.OCounter = imageJSON.OCounter newImage.CreatedAt = models.SQLiteTimestamp{Timestamp: imageJSON.CreatedAt.GetTime()} newImage.UpdatedAt = models.SQLiteTimestamp{Timestamp: imageJSON.UpdatedAt.GetTime()} diff --git a/pkg/manager/jsonschema/gallery.go b/pkg/manager/jsonschema/gallery.go index b128ee24869..db6211b7bd7 100644 --- a/pkg/manager/jsonschema/gallery.go +++ b/pkg/manager/jsonschema/gallery.go @@ -17,6 +17,7 @@ type Gallery struct { Date string `json:"date,omitempty"` Details string `json:"details,omitempty"` Rating int `json:"rating,omitempty"` + Organized bool `json:"organized,omitempty"` Studio string `json:"studio,omitempty"` Performers []string `json:"performers,omitempty"` Tags []string `json:"tags,omitempty"` diff --git a/pkg/manager/jsonschema/image.go b/pkg/manager/jsonschema/image.go index d018ab2abc3..118899ef80e 100644 --- a/pkg/manager/jsonschema/image.go +++ b/pkg/manager/jsonschema/image.go @@ -20,6 +20,7 @@ type Image struct { Checksum string `json:"checksum,omitempty"` Studio string `json:"studio,omitempty"` Rating int `json:"rating,omitempty"` + Organized bool `json:"organized,omitempty"` OCounter int `json:"o_counter,omitempty"` Galleries []string `json:"galleries,omitempty"` Performers []string `json:"performers,omitempty"` diff --git a/pkg/models/modelstest/sql.go b/pkg/models/modelstest/sql.go index d60af2fede5..dbc34bab527 100644 --- a/pkg/models/modelstest/sql.go +++ b/pkg/models/modelstest/sql.go @@ -15,3 +15,10 @@ func NullInt64(v int64) sql.NullInt64 { Valid: true, } } + +func NullBool(v bool) sql.NullBool { + return sql.NullBool{ + Bool: v, + Valid: true, + } +} diff --git a/pkg/scene/export.go b/pkg/scene/export.go index 3b0f37f5f5e..12209c8a172 100644 --- a/pkg/scene/export.go +++ b/pkg/scene/export.go @@ -43,6 +43,10 @@ func ToBasicJSON(reader models.SceneReader, scene *models.Scene) (*jsonschema.Sc newSceneJSON.Rating = int(scene.Rating.Int64) } + if scene.Organized.Valid { + newSceneJSON.Organized = scene.Organized.Bool + } + newSceneJSON.OCounter = scene.OCounter if scene.Details.Valid { diff --git a/pkg/scene/export_test.go b/pkg/scene/export_test.go index 3c591968638..46e0e17c91d 100644 --- a/pkg/scene/export_test.go +++ b/pkg/scene/export_test.go @@ -47,6 +47,7 @@ const ( date = "2001-01-01" rating = 5 ocounter = 2 + organized = true details = "details" size = "size" duration = 1.23 @@ -113,6 +114,7 @@ func createFullScene(id int) models.Scene { OCounter: ocounter, OSHash: modelstest.NullString(oshash), Rating: modelstest.NullInt64(rating), + Organized: modelstest.NullBool(organized), Size: modelstest.NullString(size), VideoCodec: modelstest.NullString(videoCodec), Width: modelstest.NullInt64(width), @@ -140,14 +142,15 @@ func createEmptyScene(id int) models.Scene { func createFullJSONScene(image string) *jsonschema.Scene { return &jsonschema.Scene{ - Title: title, - Checksum: checksum, - Date: date, - Details: details, - OCounter: ocounter, - OSHash: oshash, - Rating: rating, - URL: url, + Title: title, + Checksum: checksum, + Date: date, + Details: details, + OCounter: ocounter, + OSHash: oshash, + Rating: rating, + Organized: organized, + URL: url, File: &jsonschema.SceneFile{ AudioCodec: audioCodec, Bitrate: bitrate, diff --git a/pkg/scene/import.go b/pkg/scene/import.go index a843646c29c..49ae79f8526 100644 --- a/pkg/scene/import.go +++ b/pkg/scene/import.go @@ -90,6 +90,11 @@ func (i *Importer) sceneJSONToScene(sceneJSON jsonschema.Scene) models.Scene { newScene.Rating = sql.NullInt64{Int64: int64(sceneJSON.Rating), Valid: true} } + newScene.Organized = sql.NullBool{ + Bool: sceneJSON.Organized, + Valid: true, + } + newScene.OCounter = sceneJSON.OCounter newScene.CreatedAt = models.SQLiteTimestamp{Timestamp: sceneJSON.CreatedAt.GetTime()} newScene.UpdatedAt = models.SQLiteTimestamp{Timestamp: sceneJSON.UpdatedAt.GetTime()} From b32659c49503643c92067e82e1dba1167134f09b Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Tue, 8 Dec 2020 14:53:24 +1100 Subject: [PATCH 08/10] Make organized flag not null --- pkg/api/resolver_model_gallery.go | 7 ------- pkg/api/resolver_model_image.go | 7 ------- pkg/api/resolver_model_scene.go | 7 ------- pkg/api/resolver_mutation_gallery.go | 4 ++-- pkg/api/resolver_mutation_image.go | 4 ++-- pkg/api/resolver_mutation_scene.go | 4 ++-- pkg/database/migrations/16_organized_flag.up.sql | 6 +++--- pkg/gallery/export.go | 4 +--- pkg/gallery/export_test.go | 2 +- pkg/gallery/import.go | 6 +----- pkg/gallery/import_test.go | 2 +- pkg/image/export.go | 5 +---- pkg/image/export_test.go | 2 +- pkg/image/import.go | 5 +---- pkg/models/model_gallery.go | 4 ++-- pkg/models/model_image.go | 4 ++-- pkg/models/model_scene.go | 4 ++-- pkg/models/querybuilder_scene.go | 6 +++++- pkg/scene/export.go | 5 +---- pkg/scene/export_test.go | 2 +- pkg/scene/import.go | 6 +----- 21 files changed, 30 insertions(+), 66 deletions(-) diff --git a/pkg/api/resolver_model_gallery.go b/pkg/api/resolver_model_gallery.go index e50cea03205..0d73f82199d 100644 --- a/pkg/api/resolver_model_gallery.go +++ b/pkg/api/resolver_model_gallery.go @@ -22,13 +22,6 @@ func (r *galleryResolver) Title(ctx context.Context, obj *models.Gallery) (*stri return nil, nil } -func (r *galleryResolver) Organized(ctx context.Context, obj *models.Gallery) (bool, error) { - if obj.Organized.Valid { - return obj.Organized.Bool, nil - } - return false, nil -} - func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) ([]*models.Image, error) { qb := models.NewImageQueryBuilder() diff --git a/pkg/api/resolver_model_image.go b/pkg/api/resolver_model_image.go index d46d9ae25e8..6ca679f896d 100644 --- a/pkg/api/resolver_model_image.go +++ b/pkg/api/resolver_model_image.go @@ -21,13 +21,6 @@ func (r *imageResolver) Rating(ctx context.Context, obj *models.Image) (*int, er return nil, nil } -func (r *imageResolver) Organized(ctx context.Context, obj *models.Image) (bool, error) { - if obj.Organized.Valid { - return obj.Organized.Bool, nil - } - return false, nil -} - func (r *imageResolver) File(ctx context.Context, obj *models.Image) (*models.ImageFileType, error) { width := int(obj.Width.Int64) height := int(obj.Height.Int64) diff --git a/pkg/api/resolver_model_scene.go b/pkg/api/resolver_model_scene.go index 0bc7a29b363..656e2ef66e0 100644 --- a/pkg/api/resolver_model_scene.go +++ b/pkg/api/resolver_model_scene.go @@ -36,13 +36,6 @@ func (r *sceneResolver) Details(ctx context.Context, obj *models.Scene) (*string return nil, nil } -func (r *sceneResolver) Organized(ctx context.Context, obj *models.Scene) (bool, error) { - if obj.Organized.Valid { - return obj.Organized.Bool, nil - } - return false, nil -} - func (r *sceneResolver) URL(ctx context.Context, obj *models.Scene) (*string, error) { if obj.URL.Valid { return &obj.URL.String, nil diff --git a/pkg/api/resolver_mutation_gallery.go b/pkg/api/resolver_mutation_gallery.go index 5f1790d031c..29c8c2d4d84 100644 --- a/pkg/api/resolver_mutation_gallery.go +++ b/pkg/api/resolver_mutation_gallery.go @@ -205,7 +205,7 @@ func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, transl updatedGallery.Date = translator.sqliteDate(input.Date, "date") updatedGallery.Rating = translator.nullInt64(input.Rating, "rating") updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") - updatedGallery.Organized = translator.nullBool(input.Organized, "organized") + updatedGallery.Organized = input.Organized // gallery scene is set from the scene only @@ -273,7 +273,7 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.B updatedGallery.Rating = translator.nullInt64(input.Rating, "rating") updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") updatedGallery.SceneID = translator.nullInt64FromString(input.SceneID, "scene_id") - updatedGallery.Organized = translator.nullBool(input.Organized, "organized") + updatedGallery.Organized = input.Organized ret := []*models.Gallery{} diff --git a/pkg/api/resolver_mutation_image.go b/pkg/api/resolver_mutation_image.go index d67dc79b09e..2d2ad4adadd 100644 --- a/pkg/api/resolver_mutation_image.go +++ b/pkg/api/resolver_mutation_image.go @@ -77,7 +77,7 @@ func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, translator updatedImage.Title = translator.nullString(input.Title, "title") updatedImage.Rating = translator.nullInt64(input.Rating, "rating") updatedImage.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") - updatedImage.Organized = translator.nullBool(input.Organized, "organized") + updatedImage.Organized = input.Organized qb := models.NewImageQueryBuilder() jqb := models.NewJoinsQueryBuilder() @@ -143,7 +143,7 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.Bul updatedImage.Title = translator.nullString(input.Title, "title") updatedImage.Rating = translator.nullInt64(input.Rating, "rating") updatedImage.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") - updatedImage.Organized = translator.nullBool(input.Organized, "organized") + updatedImage.Organized = input.Organized ret := []*models.Image{} diff --git a/pkg/api/resolver_mutation_scene.go b/pkg/api/resolver_mutation_scene.go index c229b1636fa..aa0ff47a583 100644 --- a/pkg/api/resolver_mutation_scene.go +++ b/pkg/api/resolver_mutation_scene.go @@ -85,7 +85,7 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, translator updatedScene.Date = translator.sqliteDate(input.Date, "date") updatedScene.Rating = translator.nullInt64(input.Rating, "rating") updatedScene.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") - updatedScene.Organized = translator.nullBool(input.Organized, "organized") + updatedScene.Organized = input.Organized if input.CoverImage != nil && *input.CoverImage != "" { var err error @@ -243,7 +243,7 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul updatedScene.Date = translator.sqliteDate(input.Date, "date") updatedScene.Rating = translator.nullInt64(input.Rating, "rating") updatedScene.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id") - updatedScene.Organized = translator.nullBool(input.Organized, "organized") + updatedScene.Organized = input.Organized ret := []*models.Scene{} diff --git a/pkg/database/migrations/16_organized_flag.up.sql b/pkg/database/migrations/16_organized_flag.up.sql index 178fd0b546c..46395197c04 100644 --- a/pkg/database/migrations/16_organized_flag.up.sql +++ b/pkg/database/migrations/16_organized_flag.up.sql @@ -1,3 +1,3 @@ -ALTER TABLE `scenes` ADD COLUMN `organized` boolean default 'FALSE'; -ALTER TABLE `images` ADD COLUMN `organized` boolean default 'FALSE'; -ALTER TABLE `galleries` ADD COLUMN `organized` boolean default 'FALSE'; +ALTER TABLE `scenes` ADD COLUMN `organized` boolean not null default '0'; +ALTER TABLE `images` ADD COLUMN `organized` boolean not null default '0'; +ALTER TABLE `galleries` ADD COLUMN `organized` boolean not null default '0'; diff --git a/pkg/gallery/export.go b/pkg/gallery/export.go index a77ade5a119..c9dd2797d27 100644 --- a/pkg/gallery/export.go +++ b/pkg/gallery/export.go @@ -40,9 +40,7 @@ func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) { newGalleryJSON.Rating = int(gallery.Rating.Int64) } - if gallery.Organized.Valid { - newGalleryJSON.Organized = gallery.Organized.Bool - } + newGalleryJSON.Organized = gallery.Organized if gallery.Details.Valid { newGalleryJSON.Details = gallery.Details.String diff --git a/pkg/gallery/export_test.go b/pkg/gallery/export_test.go index 734e93db2cc..e0b29038a53 100644 --- a/pkg/gallery/export_test.go +++ b/pkg/gallery/export_test.go @@ -61,7 +61,7 @@ func createFullGallery(id int) models.Gallery { }, Details: modelstest.NullString(details), Rating: modelstest.NullInt64(rating), - Organized: modelstest.NullBool(organized), + Organized: organized, URL: modelstest.NullString(url), CreatedAt: models.SQLiteTimestamp{ Timestamp: createTime, diff --git a/pkg/gallery/import.go b/pkg/gallery/import.go index c77d494e158..edc75d3b7d5 100644 --- a/pkg/gallery/import.go +++ b/pkg/gallery/import.go @@ -68,11 +68,7 @@ func (i *Importer) galleryJSONToGallery(galleryJSON jsonschema.Gallery) models.G newGallery.Rating = sql.NullInt64{Int64: int64(galleryJSON.Rating), Valid: true} } - newGallery.Organized = sql.NullBool{ - Bool: galleryJSON.Organized, - Valid: true, - } - + newGallery.Organized = galleryJSON.Organized newGallery.CreatedAt = models.SQLiteTimestamp{Timestamp: galleryJSON.CreatedAt.GetTime()} newGallery.UpdatedAt = models.SQLiteTimestamp{Timestamp: galleryJSON.UpdatedAt.GetTime()} diff --git a/pkg/gallery/import_test.go b/pkg/gallery/import_test.go index ce846f6c783..d88738190a8 100644 --- a/pkg/gallery/import_test.go +++ b/pkg/gallery/import_test.go @@ -86,7 +86,7 @@ func TestImporterPreImport(t *testing.T) { }, Details: modelstest.NullString(details), Rating: modelstest.NullInt64(rating), - Organized: modelstest.NullBool(organized), + Organized: organized, URL: modelstest.NullString(url), CreatedAt: models.SQLiteTimestamp{ Timestamp: createdAt, diff --git a/pkg/image/export.go b/pkg/image/export.go index 35d2e8c3b0e..e02a505c4e9 100644 --- a/pkg/image/export.go +++ b/pkg/image/export.go @@ -23,10 +23,7 @@ func ToBasicJSON(image *models.Image) *jsonschema.Image { newImageJSON.Rating = int(image.Rating.Int64) } - if image.Organized.Valid { - newImageJSON.Organized = image.Organized.Bool - } - + newImageJSON.Organized = image.Organized newImageJSON.OCounter = image.OCounter newImageJSON.File = getImageFileJSON(image) diff --git a/pkg/image/export_test.go b/pkg/image/export_test.go index 7c07fa15225..b1283298e76 100644 --- a/pkg/image/export_test.go +++ b/pkg/image/export_test.go @@ -70,7 +70,7 @@ func createFullImage(id int) models.Image { Height: modelstest.NullInt64(height), OCounter: ocounter, Rating: modelstest.NullInt64(rating), - Organized: modelstest.NullBool(organized), + Organized: organized, Size: modelstest.NullInt64(int64(size)), Width: modelstest.NullInt64(width), CreatedAt: models.SQLiteTimestamp{ diff --git a/pkg/image/import.go b/pkg/image/import.go index 0ab3e105293..a74b561b14e 100644 --- a/pkg/image/import.go +++ b/pkg/image/import.go @@ -63,10 +63,7 @@ func (i *Importer) imageJSONToImage(imageJSON jsonschema.Image) models.Image { newImage.Rating = sql.NullInt64{Int64: int64(imageJSON.Rating), Valid: true} } - newImage.Organized = sql.NullBool{ - Bool: imageJSON.Organized, - } - + newImage.Organized = imageJSON.Organized newImage.OCounter = imageJSON.OCounter newImage.CreatedAt = models.SQLiteTimestamp{Timestamp: imageJSON.CreatedAt.GetTime()} newImage.UpdatedAt = models.SQLiteTimestamp{Timestamp: imageJSON.UpdatedAt.GetTime()} diff --git a/pkg/models/model_gallery.go b/pkg/models/model_gallery.go index c5d0678f6dc..db8383ad4d2 100644 --- a/pkg/models/model_gallery.go +++ b/pkg/models/model_gallery.go @@ -14,7 +14,7 @@ type Gallery struct { Date SQLiteDate `db:"date" json:"date"` Details sql.NullString `db:"details" json:"details"` Rating sql.NullInt64 `db:"rating" json:"rating"` - Organized sql.NullBool `db:"organized" json:"organized"` + Organized bool `db:"organized" json:"organized"` StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` SceneID sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"` FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` @@ -33,7 +33,7 @@ type GalleryPartial struct { Date *SQLiteDate `db:"date" json:"date"` Details *sql.NullString `db:"details" json:"details"` Rating *sql.NullInt64 `db:"rating" json:"rating"` - Organized *sql.NullBool `db:"organized" json:"organized"` + Organized *bool `db:"organized" json:"organized"` StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` SceneID *sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"` FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` diff --git a/pkg/models/model_image.go b/pkg/models/model_image.go index 8cc07c4e9c0..6ad13eb2fcc 100644 --- a/pkg/models/model_image.go +++ b/pkg/models/model_image.go @@ -11,7 +11,7 @@ type Image struct { Path string `db:"path" json:"path"` Title sql.NullString `db:"title" json:"title"` Rating sql.NullInt64 `db:"rating" json:"rating"` - Organized sql.NullBool `db:"organized" json:"organized"` + Organized bool `db:"organized" json:"organized"` OCounter int `db:"o_counter" json:"o_counter"` Size sql.NullInt64 `db:"size" json:"size"` Width sql.NullInt64 `db:"width" json:"width"` @@ -30,7 +30,7 @@ type ImagePartial struct { Path *string `db:"path" json:"path"` Title *sql.NullString `db:"title" json:"title"` Rating *sql.NullInt64 `db:"rating" json:"rating"` - Organized *sql.NullBool `db:"organized" json:"organized"` + Organized *bool `db:"organized" json:"organized"` Size *sql.NullInt64 `db:"size" json:"size"` Width *sql.NullInt64 `db:"width" json:"width"` Height *sql.NullInt64 `db:"height" json:"height"` diff --git a/pkg/models/model_scene.go b/pkg/models/model_scene.go index affce3628b8..5c036aee706 100644 --- a/pkg/models/model_scene.go +++ b/pkg/models/model_scene.go @@ -16,7 +16,7 @@ type Scene struct { URL sql.NullString `db:"url" json:"url"` Date SQLiteDate `db:"date" json:"date"` Rating sql.NullInt64 `db:"rating" json:"rating"` - Organized sql.NullBool `db:"organized" json:"organized"` + Organized bool `db:"organized" json:"organized"` OCounter int `db:"o_counter" json:"o_counter"` Size sql.NullString `db:"size" json:"size"` Duration sql.NullFloat64 `db:"duration" json:"duration"` @@ -45,7 +45,7 @@ type ScenePartial struct { URL *sql.NullString `db:"url" json:"url"` Date *SQLiteDate `db:"date" json:"date"` Rating *sql.NullInt64 `db:"rating" json:"rating"` - Organized *sql.NullBool `db:"organized" json:"organized"` + Organized *bool `db:"organized" json:"organized"` 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 7cd28eec301..803fa097a40 100644 --- a/pkg/models/querybuilder_scene.go +++ b/pkg/models/querybuilder_scene.go @@ -483,10 +483,14 @@ func getDurationWhereClause(durationFilter IntCriterionInput) (string, []interfa return clause, args } -func (qb *SceneQueryBuilder) QueryAllByPathRegex(regex string) ([]*Scene, error) { +func (qb *SceneQueryBuilder) QueryAllByPathRegex(regex string, ignoreOrganized bool) ([]*Scene, error) { var args []interface{} body := selectDistinctIDs("scenes") + " WHERE scenes.path regexp ?" + if ignoreOrganized { + body += " AND scenes.organized = 0" + } + args = append(args, "(?i)"+regex) idsResult, err := runIdsQuery(body, args) diff --git a/pkg/scene/export.go b/pkg/scene/export.go index 12209c8a172..c7e680eebd2 100644 --- a/pkg/scene/export.go +++ b/pkg/scene/export.go @@ -43,10 +43,7 @@ func ToBasicJSON(reader models.SceneReader, scene *models.Scene) (*jsonschema.Sc newSceneJSON.Rating = int(scene.Rating.Int64) } - if scene.Organized.Valid { - newSceneJSON.Organized = scene.Organized.Bool - } - + newSceneJSON.Organized = scene.Organized newSceneJSON.OCounter = scene.OCounter if scene.Details.Valid { diff --git a/pkg/scene/export_test.go b/pkg/scene/export_test.go index 46e0e17c91d..7ca7e2bb582 100644 --- a/pkg/scene/export_test.go +++ b/pkg/scene/export_test.go @@ -114,7 +114,7 @@ func createFullScene(id int) models.Scene { OCounter: ocounter, OSHash: modelstest.NullString(oshash), Rating: modelstest.NullInt64(rating), - Organized: modelstest.NullBool(organized), + Organized: organized, Size: modelstest.NullString(size), VideoCodec: modelstest.NullString(videoCodec), Width: modelstest.NullInt64(width), diff --git a/pkg/scene/import.go b/pkg/scene/import.go index 49ae79f8526..1e634d31f80 100644 --- a/pkg/scene/import.go +++ b/pkg/scene/import.go @@ -90,11 +90,7 @@ func (i *Importer) sceneJSONToScene(sceneJSON jsonschema.Scene) models.Scene { newScene.Rating = sql.NullInt64{Int64: int64(sceneJSON.Rating), Valid: true} } - newScene.Organized = sql.NullBool{ - Bool: sceneJSON.Organized, - Valid: true, - } - + newScene.Organized = sceneJSON.Organized newScene.OCounter = sceneJSON.OCounter newScene.CreatedAt = models.SQLiteTimestamp{Timestamp: sceneJSON.CreatedAt.GetTime()} newScene.UpdatedAt = models.SQLiteTimestamp{Timestamp: sceneJSON.UpdatedAt.GetTime()} From 6342f988e44137ae788edf060d7f9e9dd9cea5f4 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Tue, 8 Dec 2020 14:58:03 +1100 Subject: [PATCH 09/10] Ignore organized scenes for autotag --- pkg/manager/task_autotag.go | 9 ++++++--- pkg/manager/task_autotag_test.go | 10 ++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/pkg/manager/task_autotag.go b/pkg/manager/task_autotag.go index 78f37ce6984..8e4c4ab36e3 100644 --- a/pkg/manager/task_autotag.go +++ b/pkg/manager/task_autotag.go @@ -37,7 +37,8 @@ func (t *AutoTagPerformerTask) autoTagPerformer() { regex := getQueryRegex(t.performer.Name.String) - scenes, err := qb.QueryAllByPathRegex(regex) + const ignoreOrganized = true + scenes, err := qb.QueryAllByPathRegex(regex, ignoreOrganized) if err != nil { logger.Infof("Error querying scenes with regex '%s': %s", regex, err.Error()) @@ -82,7 +83,8 @@ func (t *AutoTagStudioTask) autoTagStudio() { regex := getQueryRegex(t.studio.Name.String) - scenes, err := qb.QueryAllByPathRegex(regex) + const ignoreOrganized = true + scenes, err := qb.QueryAllByPathRegex(regex, ignoreOrganized) if err != nil { logger.Infof("Error querying scenes with regex '%s': %s", regex, err.Error()) @@ -139,7 +141,8 @@ func (t *AutoTagTagTask) autoTagTag() { regex := getQueryRegex(t.tag.Name) - scenes, err := qb.QueryAllByPathRegex(regex) + const ignoreOrganized = true + scenes, err := qb.QueryAllByPathRegex(regex, ignoreOrganized) if err != nil { logger.Infof("Error querying scenes with regex '%s': %s", regex, err.Error()) diff --git a/pkg/manager/task_autotag_test.go b/pkg/manager/task_autotag_test.go index 690c056c3b7..67e428bbeb7 100644 --- a/pkg/manager/task_autotag_test.go +++ b/pkg/manager/task_autotag_test.go @@ -187,6 +187,16 @@ func createScenes(tx *sqlx.Tx) error { } } + // add organized scenes + for _, fn := range scenePatterns { + s := makeScene("organized"+fn, false) + s.Organized = true + err := createScene(sqb, tx, s) + if err != nil { + return err + } + } + // create scene with existing studio io studioScene := makeScene(existingStudioSceneName, true) studioScene.StudioID = sql.NullInt64{Valid: true, Int64: int64(existingStudioID)} From 5841b126ebe818078b49c44ca57c45fd304c823d Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Thu, 17 Dec 2020 13:50:57 +1100 Subject: [PATCH 10/10] Add changelog entry --- ui/v2.5/src/components/Changelog/versions/v050.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/v2.5/src/components/Changelog/versions/v050.md b/ui/v2.5/src/components/Changelog/versions/v050.md index eaf07f18c8b..548f9dbb836 100644 --- a/ui/v2.5/src/components/Changelog/versions/v050.md +++ b/ui/v2.5/src/components/Changelog/versions/v050.md @@ -1,4 +1,5 @@ ### ✨ New Features +* Add organized flag for scenes, galleries and images. * Allow configuration of visible navbar items. ### 🎨 Improvements