diff --git a/internal/api/resolver_mutation_gallery.go b/internal/api/resolver_mutation_gallery.go index 55723428e..aa567734d 100644 --- a/internal/api/resolver_mutation_gallery.go +++ b/internal/api/resolver_mutation_gallery.go @@ -338,6 +338,10 @@ func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.Gall return fmt.Errorf("gallery with id %d not found", id) } + if err := gallery.LoadFiles(ctx, qb); err != nil { + return fmt.Errorf("loading files for gallery %d", id) + } + galleries = append(galleries, gallery) imgsDestroyed, err = r.galleryService.Destroy(ctx, gallery, fileDeleter, deleteGenerated, deleteFile) diff --git a/internal/manager/task_clean.go b/internal/manager/task_clean.go index d2b387b8f..61076b15d 100644 --- a/internal/manager/task_clean.go +++ b/internal/manager/task_clean.go @@ -13,6 +13,7 @@ import ( "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/job" "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/plugin" "github.com/stashapp/stash/pkg/scene" ) @@ -172,13 +173,13 @@ type cleanHandler struct { } func (h *cleanHandler) HandleFile(ctx context.Context, fileDeleter *file.Deleter, fileID file.ID) error { - if err := h.deleteRelatedScenes(ctx, fileDeleter, fileID); err != nil { + if err := h.handleRelatedScenes(ctx, fileDeleter, fileID); err != nil { return err } - if err := h.deleteRelatedGalleries(ctx, fileID); err != nil { + if err := h.handleRelatedGalleries(ctx, fileID); err != nil { return err } - if err := h.deleteRelatedImages(ctx, fileDeleter, fileID); err != nil { + if err := h.handleRelatedImages(ctx, fileDeleter, fileID); err != nil { return err } @@ -189,7 +190,7 @@ func (h *cleanHandler) HandleFolder(ctx context.Context, fileDeleter *file.Delet return h.deleteRelatedFolderGalleries(ctx, folderID) } -func (h *cleanHandler) deleteRelatedScenes(ctx context.Context, fileDeleter *file.Deleter, fileID file.ID) error { +func (h *cleanHandler) handleRelatedScenes(ctx context.Context, fileDeleter *file.Deleter, fileID file.ID) error { mgr := GetInstance() sceneQB := mgr.Database.Scene scenes, err := sceneQB.FindByFileID(ctx, fileID) @@ -225,13 +226,28 @@ func (h *cleanHandler) deleteRelatedScenes(ctx context.Context, fileDeleter *fil OSHash: oshash, Path: scene.Path, }, nil) + } else { + // set the primary file to a remaining file + var newPrimaryID file.ID + for _, f := range scene.Files.List() { + if f.ID != fileID { + newPrimaryID = f.ID + break + } + } + + if _, err := mgr.Repository.Scene.UpdatePartial(ctx, scene.ID, models.ScenePartial{ + PrimaryFileID: &newPrimaryID, + }); err != nil { + return err + } } } return nil } -func (h *cleanHandler) deleteRelatedGalleries(ctx context.Context, fileID file.ID) error { +func (h *cleanHandler) handleRelatedGalleries(ctx context.Context, fileID file.ID) error { mgr := GetInstance() qb := mgr.Database.Gallery galleries, err := qb.FindByFileID(ctx, fileID) @@ -255,6 +271,21 @@ func (h *cleanHandler) deleteRelatedGalleries(ctx context.Context, fileID file.I Checksum: g.Checksum(), Path: g.Path, }, nil) + } else { + // set the primary file to a remaining file + var newPrimaryID file.ID + for _, f := range g.Files.List() { + if f.Base().ID != fileID { + newPrimaryID = f.Base().ID + break + } + } + + if _, err := mgr.Repository.Gallery.UpdatePartial(ctx, g.ID, models.GalleryPartial{ + PrimaryFileID: &newPrimaryID, + }); err != nil { + return err + } } } @@ -276,15 +307,16 @@ func (h *cleanHandler) deleteRelatedFolderGalleries(ctx context.Context, folderI } mgr.PluginCache.RegisterPostHooks(ctx, mgr.Database, g.ID, plugin.GalleryDestroyPost, plugin.GalleryDestroyInput{ - Checksum: g.Checksum(), - Path: g.Path, + // No checksum for folders + // Checksum: g.Checksum(), + Path: g.Path, }, nil) } return nil } -func (h *cleanHandler) deleteRelatedImages(ctx context.Context, fileDeleter *file.Deleter, fileID file.ID) error { +func (h *cleanHandler) handleRelatedImages(ctx context.Context, fileDeleter *file.Deleter, fileID file.ID) error { mgr := GetInstance() imageQB := mgr.Database.Image images, err := imageQB.FindByFileID(ctx, fileID) @@ -312,6 +344,21 @@ func (h *cleanHandler) deleteRelatedImages(ctx context.Context, fileDeleter *fil Checksum: i.Checksum, Path: i.Path, }, nil) + } else { + // set the primary file to a remaining file + var newPrimaryID file.ID + for _, f := range i.Files.List() { + if f.Base().ID != fileID { + newPrimaryID = f.Base().ID + break + } + } + + if _, err := mgr.Repository.Image.UpdatePartial(ctx, i.ID, models.ImagePartial{ + PrimaryFileID: &newPrimaryID, + }); err != nil { + return err + } } } diff --git a/internal/manager/task_export.go b/internal/manager/task_export.go index 69bbc2d36..225938e70 100644 --- a/internal/manager/task_export.go +++ b/internal/manager/task_export.go @@ -335,7 +335,7 @@ func (t *ExportTask) populateGalleryImages(ctx context.Context, repo Repository) images, err := imageReader.FindByGalleryID(ctx, g.ID) if err != nil { - logger.Errorf("[galleries] <%s> failed to fetch images for gallery: %s", g.Checksum, err.Error()) + logger.Errorf("[galleries] <%s> failed to fetch images for gallery: %s", g.Checksum(), err.Error()) continue } diff --git a/pkg/models/model_gallery.go b/pkg/models/model_gallery.go index 05661d017..4da6583f8 100644 --- a/pkg/models/model_gallery.go +++ b/pkg/models/model_gallery.go @@ -107,9 +107,10 @@ type GalleryPartial struct { CreatedAt OptionalTime UpdatedAt OptionalTime - SceneIDs *UpdateIDs - TagIDs *UpdateIDs - PerformerIDs *UpdateIDs + SceneIDs *UpdateIDs + TagIDs *UpdateIDs + PerformerIDs *UpdateIDs + PrimaryFileID *file.ID } func NewGalleryPartial() GalleryPartial { diff --git a/pkg/models/model_image.go b/pkg/models/model_image.go index 20ae370d0..377e0cc5a 100644 --- a/pkg/models/model_image.go +++ b/pkg/models/model_image.go @@ -121,9 +121,10 @@ type ImagePartial struct { CreatedAt OptionalTime UpdatedAt OptionalTime - GalleryIDs *UpdateIDs - TagIDs *UpdateIDs - PerformerIDs *UpdateIDs + GalleryIDs *UpdateIDs + TagIDs *UpdateIDs + PerformerIDs *UpdateIDs + PrimaryFileID *file.ID } func NewImagePartial() ImagePartial { diff --git a/pkg/models/model_scene.go b/pkg/models/model_scene.go index 4057a5006..f2dfbf28d 100644 --- a/pkg/models/model_scene.go +++ b/pkg/models/model_scene.go @@ -143,11 +143,12 @@ type ScenePartial struct { CreatedAt OptionalTime UpdatedAt OptionalTime - GalleryIDs *UpdateIDs - TagIDs *UpdateIDs - PerformerIDs *UpdateIDs - MovieIDs *UpdateMovieIDs - StashIDs *UpdateStashIDs + GalleryIDs *UpdateIDs + TagIDs *UpdateIDs + PerformerIDs *UpdateIDs + MovieIDs *UpdateMovieIDs + StashIDs *UpdateStashIDs + PrimaryFileID *file.ID } func NewScenePartial() ScenePartial { diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go index 2c2c2682c..995f37cb8 100644 --- a/pkg/sqlite/gallery.go +++ b/pkg/sqlite/gallery.go @@ -246,6 +246,12 @@ func (qb *GalleryStore) UpdatePartial(ctx context.Context, id int, partial model } } + if partial.PrimaryFileID != nil { + if err := galleriesFilesTableMgr.setPrimary(ctx, id, *partial.PrimaryFileID); err != nil { + return nil, err + } + } + return qb.Find(ctx, id) } diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go index cd37847bd..5d2eb22fd 100644 --- a/pkg/sqlite/image.go +++ b/pkg/sqlite/image.go @@ -193,6 +193,12 @@ func (qb *ImageStore) UpdatePartial(ctx context.Context, id int, partial models. } } + if partial.PrimaryFileID != nil { + if err := imagesFilesTableMgr.setPrimary(ctx, id, *partial.PrimaryFileID); err != nil { + return nil, err + } + } + return qb.find(ctx, id) } diff --git a/pkg/sqlite/migrations/32_files.up.sql b/pkg/sqlite/migrations/32_files.up.sql index 3644541b8..708034bd7 100644 --- a/pkg/sqlite/migrations/32_files.up.sql +++ b/pkg/sqlite/migrations/32_files.up.sql @@ -86,6 +86,7 @@ CREATE TABLE `images_files` ( ); CREATE INDEX `index_images_files_on_file_id` on `images_files` (`file_id`); +CREATE UNIQUE INDEX `unique_index_images_files_on_primary` on `images_files` (`image_id`) WHERE `primary` = 1; CREATE TABLE `galleries_files` ( `gallery_id` integer NOT NULL, @@ -97,6 +98,7 @@ CREATE TABLE `galleries_files` ( ); CREATE INDEX `index_galleries_files_file_id` ON `galleries_files` (`file_id`); +CREATE UNIQUE INDEX `unique_index_galleries_files_on_primary` on `galleries_files` (`gallery_id`) WHERE `primary` = 1; CREATE TABLE `scenes_files` ( `scene_id` integer NOT NULL, @@ -108,6 +110,7 @@ CREATE TABLE `scenes_files` ( ); CREATE INDEX `index_scenes_files_file_id` ON `scenes_files` (`file_id`); +CREATE UNIQUE INDEX `unique_index_scenes_files_on_primary` on `scenes_files` (`scene_id`) WHERE `primary` = 1; PRAGMA foreign_keys=OFF; diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index 710e784e5..f096db130 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -256,6 +256,11 @@ func (qb *SceneStore) UpdatePartial(ctx context.Context, id int, partial models. return nil, err } } + if partial.PrimaryFileID != nil { + if err := scenesFilesTableMgr.setPrimary(ctx, id, *partial.PrimaryFileID); err != nil { + return nil, err + } + } return qb.Find(ctx, id) } diff --git a/pkg/sqlite/table.go b/pkg/sqlite/table.go index f4cec8244..c0b08fa6b 100644 --- a/pkg/sqlite/table.go +++ b/pkg/sqlite/table.go @@ -522,6 +522,28 @@ func (t *relatedFilesTable) replaceJoins(ctx context.Context, id int, fileIDs [] return t.insertJoins(ctx, id, firstPrimary, fileIDs) } +func (t *relatedFilesTable) setPrimary(ctx context.Context, id int, fileID file.ID) error { + table := t.table.table + + q := dialect.Update(table).Prepared(true).Set(goqu.Record{ + "primary": 0, + }).Where(t.idColumn.Eq(id), table.Col(fileIDColumn).Neq(fileID)) + + if _, err := exec(ctx, q); err != nil { + return fmt.Errorf("unsetting primary flags in %s: %w", t.table.table.GetTable(), err) + } + + q = dialect.Update(table).Prepared(true).Set(goqu.Record{ + "primary": 1, + }).Where(t.idColumn.Eq(id), table.Col(fileIDColumn).Eq(fileID)) + + if _, err := exec(ctx, q); err != nil { + return fmt.Errorf("setting primary flag in %s: %w", t.table.table.GetTable(), err) + } + + return nil +} + type sqler interface { ToSQL() (sql string, params []interface{}, err error) } diff --git a/ui/v2.5/src/components/Setup/Migrate.tsx b/ui/v2.5/src/components/Setup/Migrate.tsx index 05f9efece..3b2226a71 100644 --- a/ui/v2.5/src/components/Setup/Migrate.tsx +++ b/ui/v2.5/src/components/Setup/Migrate.tsx @@ -60,7 +60,7 @@ export const Migrate: React.FC = () => { return; const notes = []; - for (let i = status.databaseSchema; i <= status.appSchema; ++i) { + for (let i = status.databaseSchema + 1; i <= status.appSchema; ++i) { const note = migrationNotes[i]; if (note) { notes.push(note);