mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
[Files Refactor] Set primary flag when cleaning (#2880)
* Ensure single primary per object * Set primary file during clean * Only show migration notes for actual migrations
This commit is contained in:
parent
7159ab69a3
commit
6b0bcdea88
12 changed files with 117 additions and 21 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue