[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:
WithoutPants 2022-09-05 11:46:18 +10:00
parent 7159ab69a3
commit 6b0bcdea88
12 changed files with 117 additions and 21 deletions

View file

@ -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)

View file

@ -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
}
}
}

View file

@ -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
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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;

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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);