diff --git a/internal/api/resolver_mutation_gallery.go b/internal/api/resolver_mutation_gallery.go index 5bd0bbc4d..55723428e 100644 --- a/internal/api/resolver_mutation_gallery.go +++ b/internal/api/resolver_mutation_gallery.go @@ -338,10 +338,6 @@ 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 err - } - galleries = append(galleries, gallery) imgsDestroyed, err = r.galleryService.Destroy(ctx, gallery, fileDeleter, deleteGenerated, deleteFile) diff --git a/internal/api/resolver_mutation_stash_box.go b/internal/api/resolver_mutation_stash_box.go index c3647399a..22cc1799e 100644 --- a/internal/api/resolver_mutation_stash_box.go +++ b/internal/api/resolver_mutation_stash_box.go @@ -58,10 +58,6 @@ func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input S return err } - if err := scene.LoadStashIDs(ctx, qb); err != nil { - return err - } - filepath := manager.GetInstance().Paths.Scene.GetScreenshotPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm())) res, err = client.SubmitSceneDraft(ctx, scene, boxes[input.StashBoxIndex].Endpoint, filepath) diff --git a/internal/autotag/gallery.go b/internal/autotag/gallery.go index 7e6982cbb..d2a8c2c5d 100644 --- a/internal/autotag/gallery.go +++ b/internal/autotag/gallery.go @@ -31,7 +31,7 @@ func getGalleryFileTagger(s *models.Gallery, cache *match.Cache) tagger { return tagger{ ID: s.ID, Type: "gallery", - Name: s.GetTitle(), + Name: s.DisplayName(), Path: path, trimExt: trimExt, cache: cache, diff --git a/internal/autotag/image.go b/internal/autotag/image.go index a85807bcd..404640786 100644 --- a/internal/autotag/image.go +++ b/internal/autotag/image.go @@ -23,7 +23,7 @@ func getImageFileTagger(s *models.Image, cache *match.Cache) tagger { return tagger{ ID: s.ID, Type: "image", - Name: s.GetTitle(), + Name: s.DisplayName(), Path: s.Path, cache: cache, } diff --git a/internal/autotag/scene.go b/internal/autotag/scene.go index d8b160b41..285ff7d7d 100644 --- a/internal/autotag/scene.go +++ b/internal/autotag/scene.go @@ -23,7 +23,7 @@ func getSceneFileTagger(s *models.Scene, cache *match.Cache) tagger { return tagger{ ID: s.ID, Type: "scene", - Name: s.GetTitle(), + Name: s.DisplayName(), Path: s.Path, cache: cache, } diff --git a/internal/autotag/tagger.go b/internal/autotag/tagger.go index c0c25d62c..0e53200ec 100644 --- a/internal/autotag/tagger.go +++ b/internal/autotag/tagger.go @@ -121,11 +121,11 @@ func (t *tagger) tagScenes(ctx context.Context, paths []string, sceneReader scen added, err := addFunc(p) if err != nil { - return t.addError("scene", p.GetTitle(), err) + return t.addError("scene", p.DisplayName(), err) } if added { - t.addLog("scene", p.GetTitle()) + t.addLog("scene", p.DisplayName()) } } @@ -142,11 +142,11 @@ func (t *tagger) tagImages(ctx context.Context, paths []string, imageReader imag added, err := addFunc(p) if err != nil { - return t.addError("image", p.GetTitle(), err) + return t.addError("image", p.DisplayName(), err) } if added { - t.addLog("image", p.GetTitle()) + t.addLog("image", p.DisplayName()) } } @@ -163,11 +163,11 @@ func (t *tagger) tagGalleries(ctx context.Context, paths []string, galleryReader added, err := addFunc(p) if err != nil { - return t.addError("gallery", p.GetTitle(), err) + return t.addError("gallery", p.DisplayName(), err) } if added { - t.addLog("gallery", p.GetTitle()) + t.addLog("gallery", p.DisplayName()) } } diff --git a/internal/manager/task_clean.go b/internal/manager/task_clean.go index 9ad292e15..d2b387b8f 100644 --- a/internal/manager/task_clean.go +++ b/internal/manager/task_clean.go @@ -212,7 +212,7 @@ func (h *cleanHandler) deleteRelatedScenes(ctx context.Context, fileDeleter *fil // only delete if the scene has no other files if len(scene.Files.List()) <= 1 { - logger.Infof("Deleting scene %q since it has no other related files", scene.GetTitle()) + logger.Infof("Deleting scene %q since it has no other related files", scene.DisplayName()) if err := mgr.SceneService.Destroy(ctx, scene, sceneFileDeleter, true, false); err != nil { return err } @@ -246,7 +246,7 @@ func (h *cleanHandler) deleteRelatedGalleries(ctx context.Context, fileID file.I // only delete if the gallery has no other files if len(g.Files.List()) <= 1 { - logger.Infof("Deleting gallery %q since it has no other related files", g.GetTitle()) + logger.Infof("Deleting gallery %q since it has no other related files", g.DisplayName()) if err := qb.Destroy(ctx, g.ID); err != nil { return err } @@ -270,7 +270,7 @@ func (h *cleanHandler) deleteRelatedFolderGalleries(ctx context.Context, folderI } for _, g := range galleries { - logger.Infof("Deleting folder-based gallery %q since the folder no longer exists", g.GetTitle()) + logger.Infof("Deleting folder-based gallery %q since the folder no longer exists", g.DisplayName()) if err := qb.Destroy(ctx, g.ID); err != nil { return err } @@ -303,7 +303,7 @@ func (h *cleanHandler) deleteRelatedImages(ctx context.Context, fileDeleter *fil } if len(i.Files.List()) <= 1 { - logger.Infof("Deleting image %q since it has no other related files", i.GetTitle()) + logger.Infof("Deleting image %q since it has no other related files", i.DisplayName()) if err := mgr.ImageService.Destroy(ctx, i, imageFileDeleter, true, false); err != nil { return err } diff --git a/internal/manager/task_export.go b/internal/manager/task_export.go index 37382eed8..69bbc2d36 100644 --- a/internal/manager/task_export.go +++ b/internal/manager/task_export.go @@ -329,7 +329,7 @@ func (t *ExportTask) populateGalleryImages(ctx context.Context, repo Repository) for _, g := range galleries { if err := g.LoadFiles(ctx, reader); err != nil { - logger.Errorf("[galleries] <%s> failed to fetch files for gallery: %s", g.GetTitle(), err.Error()) + logger.Errorf("[galleries] <%s> failed to fetch files for gallery: %s", g.DisplayName(), err.Error()) continue } @@ -761,7 +761,7 @@ func exportGallery(ctx context.Context, wg *sync.WaitGroup, jobChan <-chan *mode for g := range jobChan { if err := g.LoadFiles(ctx, repo.Gallery); err != nil { - logger.Errorf("[galleries] <%s> failed to fetch files for gallery: %s", g.GetTitle(), err.Error()) + logger.Errorf("[galleries] <%s> failed to fetch files for gallery: %s", g.DisplayName(), err.Error()) continue } diff --git a/pkg/gallery/delete.go b/pkg/gallery/delete.go index 7a1cc8f43..b6c1333ba 100644 --- a/pkg/gallery/delete.go +++ b/pkg/gallery/delete.go @@ -40,6 +40,10 @@ func (s *Service) Destroy(ctx context.Context, i *models.Gallery, fileDeleter *i } func (s *Service) destroyZipFileImages(ctx context.Context, i *models.Gallery, fileDeleter *image.FileDeleter, deleteGenerated, deleteFile bool) ([]*models.Image, error) { + if err := i.LoadFiles(ctx, s.Repository); err != nil { + return nil, err + } + var imgsDestroyed []*models.Image destroyer := &file.ZipDestroyer{ diff --git a/pkg/gallery/scan.go b/pkg/gallery/scan.go index 1ec6bf521..5b63faa87 100644 --- a/pkg/gallery/scan.go +++ b/pkg/gallery/scan.go @@ -97,7 +97,7 @@ func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models. } if !found { - logger.Infof("Adding %s to gallery %s", f.Base().Path, i.GetTitle()) + logger.Infof("Adding %s to gallery %s", f.Base().Path, i.DisplayName()) if err := h.CreatorUpdater.AddFileID(ctx, i.ID, f.Base().ID); err != nil { return fmt.Errorf("adding file to gallery: %w", err) diff --git a/pkg/gallery/service.go b/pkg/gallery/service.go index 53a356ce3..7d0fb3240 100644 --- a/pkg/gallery/service.go +++ b/pkg/gallery/service.go @@ -15,6 +15,7 @@ type FinderByFile interface { type Repository interface { FinderByFile Destroy(ctx context.Context, id int) error + models.FileLoader } type ImageFinder interface { diff --git a/pkg/image/delete.go b/pkg/image/delete.go index aab058c89..b61e77045 100644 --- a/pkg/image/delete.go +++ b/pkg/image/delete.go @@ -81,6 +81,10 @@ func (s *Service) destroyImage(ctx context.Context, i *models.Image, fileDeleter // deleteFiles deletes files for the image from the database and file system, if they are not in use by other images func (s *Service) deleteFiles(ctx context.Context, i *models.Image, fileDeleter *FileDeleter) error { + if err := i.LoadFiles(ctx, s.Repository); err != nil { + return err + } + for _, f := range i.Files.List() { // only delete files where there is no other associated image otherImages, err := s.Repository.FindByFileID(ctx, f.ID) diff --git a/pkg/image/scan.go b/pkg/image/scan.go index a630926ae..49bf99215 100644 --- a/pkg/image/scan.go +++ b/pkg/image/scan.go @@ -159,7 +159,7 @@ func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models. } if !found { - logger.Infof("Adding %s to image %s", f.Path, i.GetTitle()) + logger.Infof("Adding %s to image %s", f.Path, i.DisplayName()) // associate with folder-based gallery if applicable if h.ScanConfig.GetCreateGalleriesFromFolders() { diff --git a/pkg/models/model_gallery.go b/pkg/models/model_gallery.go index 7b5e636c6..05661d017 100644 --- a/pkg/models/model_gallery.go +++ b/pkg/models/model_gallery.go @@ -2,6 +2,7 @@ package models import ( "context" + "strconv" "time" "github.com/stashapp/stash/pkg/file" @@ -128,6 +129,20 @@ func (g Gallery) GetTitle() string { return g.Path } +// DisplayName returns a display name for the scene for logging purposes. +// It returns the path or title, or otherwise it returns the ID if both of these are empty. +func (g Gallery) DisplayName() string { + if g.Path != "" { + return g.Path + } + + if g.Title != "" { + return g.Title + } + + return strconv.Itoa(g.ID) +} + const DefaultGthumbWidth int = 640 type Galleries []*Gallery diff --git a/pkg/models/model_image.go b/pkg/models/model_image.go index ff3fad48f..20ae370d0 100644 --- a/pkg/models/model_image.go +++ b/pkg/models/model_image.go @@ -4,6 +4,7 @@ import ( "context" "errors" "path/filepath" + "strconv" "time" "github.com/stashapp/stash/pkg/file" @@ -96,6 +97,16 @@ func (i Image) GetTitle() string { return "" } +// DisplayName returns a display name for the scene for logging purposes. +// It returns Path if not empty, otherwise it returns the ID. +func (i Image) DisplayName() string { + if i.Path != "" { + return i.Path + } + + return strconv.Itoa(i.ID) +} + type ImageCreateInput struct { *Image FileIDs []file.ID diff --git a/pkg/models/model_scene.go b/pkg/models/model_scene.go index a278013d5..4057a5006 100644 --- a/pkg/models/model_scene.go +++ b/pkg/models/model_scene.go @@ -222,6 +222,16 @@ func (s Scene) GetTitle() string { return filepath.Base(s.Path) } +// DisplayName returns a display name for the scene for logging purposes. +// It returns Path if not empty, otherwise it returns the ID. +func (s Scene) DisplayName() string { + if s.Path != "" { + return s.Path + } + + return strconv.Itoa(s.ID) +} + // GetHash returns the hash of the scene, based on the hash algorithm provided. If // hash algorithm is MD5, then Checksum is returned. Otherwise, OSHash is returned. func (s Scene) GetHash(hashAlgorithm HashAlgorithm) string { diff --git a/pkg/scene/delete.go b/pkg/scene/delete.go index 6031b05d8..47449f1e3 100644 --- a/pkg/scene/delete.go +++ b/pkg/scene/delete.go @@ -161,6 +161,10 @@ func (s *Service) Destroy(ctx context.Context, scene *models.Scene, fileDeleter // deleteFiles deletes files from the database and file system func (s *Service) deleteFiles(ctx context.Context, scene *models.Scene, fileDeleter *FileDeleter) error { + if err := scene.LoadFiles(ctx, s.Repository); err != nil { + return err + } + for _, f := range scene.Files.List() { // only delete files where there is no other associated scene otherScenes, err := s.Repository.FindByFileID(ctx, f.ID) diff --git a/pkg/scene/scan.go b/pkg/scene/scan.go index c39cf17a9..bc9e7bd2e 100644 --- a/pkg/scene/scan.go +++ b/pkg/scene/scan.go @@ -130,7 +130,7 @@ func (h *ScanHandler) associateExisting(ctx context.Context, existing []*models. } if !found { - logger.Infof("Adding %s to scene %s", f.Path, s.GetTitle()) + logger.Infof("Adding %s to scene %s", f.Path, s.DisplayName()) if err := h.CreatorUpdater.AddFileID(ctx, s.ID, f.ID); err != nil { return fmt.Errorf("adding file to scene: %w", err) diff --git a/pkg/scene/service.go b/pkg/scene/service.go index 03cd7fa04..8d2e5dc0c 100644 --- a/pkg/scene/service.go +++ b/pkg/scene/service.go @@ -14,6 +14,7 @@ type FinderByFile interface { type Repository interface { FinderByFile Destroyer + models.VideoFileLoader } type Service struct { diff --git a/pkg/scraper/stashbox/stash_box.go b/pkg/scraper/stashbox/stash_box.go index 315d5d5a4..5133bb172 100644 --- a/pkg/scraper/stashbox/stash_box.go +++ b/pkg/scraper/stashbox/stash_box.go @@ -806,6 +806,10 @@ func (c Client) SubmitSceneDraft(ctx context.Context, scene *models.Scene, endpo fingerprints := []*graphql.FingerprintInput{} // submit all file fingerprints + if err := scene.LoadFiles(ctx, r.Scene); err != nil { + return nil, err + } + for _, f := range scene.Files.List() { duration := f.Duration @@ -888,6 +892,10 @@ func (c Client) SubmitSceneDraft(ctx context.Context, scene *models.Scene, endpo } } + if err := scene.LoadStashIDs(ctx, r.Scene); err != nil { + return nil, err + } + stashIDs := scene.StashIDs.List() var stashID *string for _, v := range stashIDs { diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go index 86aad3fbe..2c2c2682c 100644 --- a/pkg/sqlite/gallery.go +++ b/pkg/sqlite/gallery.go @@ -299,6 +299,7 @@ func (qb *GalleryStore) get(ctx context.Context, q *goqu.SelectDataset) (*models func (qb *GalleryStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*models.Gallery, error) { const single = false var ret []*models.Gallery + var lastID int if err := queryFunc(ctx, q, single, func(r *sqlx.Rows) error { var f galleryQueryRow if err := r.StructScan(&f); err != nil { @@ -307,6 +308,11 @@ func (qb *GalleryStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]* s := f.resolve() + if s.ID == lastID { + return fmt.Errorf("internal error: multiple rows returned for single gallery id %d", s.ID) + } + lastID = s.ID + ret = append(ret, s) return nil }); err != nil { diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go index 8459aa674..cd37847bd 100644 --- a/pkg/sqlite/image.go +++ b/pkg/sqlite/image.go @@ -315,6 +315,7 @@ func (qb *ImageStore) get(ctx context.Context, q *goqu.SelectDataset) (*models.I func (qb *ImageStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*models.Image, error) { const single = false var ret []*models.Image + var lastID int if err := queryFunc(ctx, q, single, func(r *sqlx.Rows) error { var f imageQueryRow if err := r.StructScan(&f); err != nil { @@ -323,6 +324,11 @@ func (qb *ImageStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*mo i := f.resolve() + if i.ID == lastID { + return fmt.Errorf("internal error: multiple rows returned for single image id %d", i.ID) + } + lastID = i.ID + ret = append(ret, i) return nil }); err != nil { diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index 27323f642..710e784e5 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -410,6 +410,7 @@ func (qb *SceneStore) get(ctx context.Context, q *goqu.SelectDataset) (*models.S func (qb *SceneStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*models.Scene, error) { const single = false var ret []*models.Scene + var lastID int if err := queryFunc(ctx, q, single, func(r *sqlx.Rows) error { var f sceneQueryRow if err := r.StructScan(&f); err != nil { @@ -417,6 +418,10 @@ func (qb *SceneStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*mo } s := f.resolve() + if s.ID == lastID { + return fmt.Errorf("internal error: multiple rows returned for single scene id %d", s.ID) + } + lastID = s.ID ret = append(ret, s) return nil