Update gallery UpdatedAt timestamp on contents change (#3771)

* Update gallery updatedAt on content change
* Update gallery in UI on image change
This commit is contained in:
WithoutPants 2023-05-31 11:06:01 +10:00 committed by GitHub
parent 9c8a6ee495
commit 74cef93d19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 187 additions and 6 deletions

View File

@ -10,6 +10,7 @@ import (
"github.com/stashapp/stash/pkg/image"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin"
"github.com/stashapp/stash/pkg/sliceutil/intslice"
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
"github.com/stashapp/stash/pkg/utils"
)
@ -138,6 +139,8 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp
}
}
var updatedGalleryIDs []int
if translator.hasField("gallery_ids") {
updatedImage.GalleryIDs, err = translateUpdateIDs(input.GalleryIds, models.RelationshipUpdateModeSet)
if err != nil {
@ -152,6 +155,8 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp
if err := r.galleryService.ValidateImageGalleryChange(ctx, i, *updatedImage.GalleryIDs); err != nil {
return nil, err
}
updatedGalleryIDs = updatedImage.GalleryIDs.ImpactedIDs(i.GalleryIDs.List())
}
if translator.hasField("performer_ids") {
@ -174,6 +179,13 @@ func (r *mutationResolver) imageUpdate(ctx context.Context, input ImageUpdateInp
return nil, err
}
// #3759 - update all impacted galleries
for _, galleryID := range updatedGalleryIDs {
if err := r.galleryService.Updated(ctx, galleryID); err != nil {
return nil, fmt.Errorf("updating gallery %d: %w", galleryID, err)
}
}
return image, nil
}
@ -223,6 +235,7 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageU
// Start the transaction and save the image marker
if err := r.withTxn(ctx, func(ctx context.Context) error {
var updatedGalleryIDs []int
qb := r.repository.Image
for _, imageID := range imageIDs {
@ -244,6 +257,9 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageU
if err := r.galleryService.ValidateImageGalleryChange(ctx, i, *updatedImage.GalleryIDs); err != nil {
return err
}
thisUpdatedGalleryIDs := updatedImage.GalleryIDs.ImpactedIDs(i.GalleryIDs.List())
updatedGalleryIDs = intslice.IntAppendUniques(updatedGalleryIDs, thisUpdatedGalleryIDs)
}
image, err := qb.UpdatePartial(ctx, imageID, updatedImage)
@ -254,6 +270,13 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageU
ret = append(ret, image)
}
// #3759 - update all impacted galleries
for _, galleryID := range updatedGalleryIDs {
if err := r.galleryService.Updated(ctx, galleryID); err != nil {
return fmt.Errorf("updating gallery %d: %w", galleryID, err)
}
}
return nil
}); err != nil {
return nil, err

View File

@ -113,4 +113,6 @@ type GalleryService interface {
Destroy(ctx context.Context, i *models.Gallery, fileDeleter *image.FileDeleter, deleteGenerated, deleteFile bool) ([]*models.Image, error)
ValidateImageGalleryChange(ctx context.Context, i *models.Image, updateIDs models.UpdateIDs) error
Updated(ctx context.Context, galleryID int) error
}

View File

@ -18,6 +18,11 @@ type Repository interface {
Destroy(ctx context.Context, id int) error
models.FileLoader
ImageUpdater
PartialUpdater
}
type PartialUpdater interface {
UpdatePartial(ctx context.Context, id int, updatedGallery models.GalleryPartial) (*models.Gallery, error)
}
type ImageFinder interface {

View File

@ -2,20 +2,25 @@ package gallery
import (
"context"
"fmt"
"time"
"github.com/stashapp/stash/pkg/models"
)
type PartialUpdater interface {
UpdatePartial(ctx context.Context, id int, updatedGallery models.GalleryPartial) (*models.Gallery, error)
}
type ImageUpdater interface {
GetImageIDs(ctx context.Context, galleryID int) ([]int, error)
AddImages(ctx context.Context, galleryID int, imageIDs ...int) error
RemoveImages(ctx context.Context, galleryID int, imageIDs ...int) error
}
func (s *Service) Updated(ctx context.Context, galleryID int) error {
_, err := s.Repository.UpdatePartial(ctx, galleryID, models.GalleryPartial{
UpdatedAt: models.NewOptionalTime(time.Now()),
})
return err
}
// AddImages adds images to the provided gallery.
// It returns an error if the gallery does not support adding images, or if
// the operation fails.
@ -24,7 +29,12 @@ func (s *Service) AddImages(ctx context.Context, g *models.Gallery, toAdd ...int
return err
}
return s.Repository.AddImages(ctx, g.ID, toAdd...)
if err := s.Repository.AddImages(ctx, g.ID, toAdd...); err != nil {
return fmt.Errorf("failed to add images to gallery: %w", err)
}
// #3759 - update the gallery's UpdatedAt timestamp
return s.Updated(ctx, g.ID)
}
// RemoveImages removes images from the provided gallery.
@ -36,7 +46,12 @@ func (s *Service) RemoveImages(ctx context.Context, g *models.Gallery, toRemove
return err
}
return s.Repository.RemoveImages(ctx, g.ID, toRemove...)
if err := s.Repository.RemoveImages(ctx, g.ID, toRemove...); err != nil {
return fmt.Errorf("failed to remove images from gallery: %w", err)
}
// #3759 - update the gallery's UpdatedAt timestamp
return s.Updated(ctx, g.ID)
}
func AddPerformer(ctx context.Context, qb PartialUpdater, o *models.Gallery, performerID int) error {

View File

@ -64,6 +64,48 @@ func (u *UpdateIDs) IDStrings() []string {
return intslice.IntSliceToStringSlice(u.IDs)
}
// GetImpactedIDs returns the IDs that will be impacted by the update.
// If the update is to add IDs, then the impacted IDs are the IDs being added.
// If the update is to remove IDs, then the impacted IDs are the IDs being removed.
// If the update is to set IDs, then the impacted IDs are the IDs being removed and the IDs being added.
// Any IDs that are already present and are being added are not returned.
// Likewise, any IDs that are not present that are being removed are not returned.
func (u *UpdateIDs) ImpactedIDs(existing []int) []int {
if u == nil {
return nil
}
switch u.Mode {
case RelationshipUpdateModeAdd:
return intslice.IntExclude(u.IDs, existing)
case RelationshipUpdateModeRemove:
return intslice.IntIntercect(existing, u.IDs)
case RelationshipUpdateModeSet:
// get the difference between the two lists
return intslice.IntNotIntersect(existing, u.IDs)
}
return nil
}
// GetEffectiveIDs returns the new IDs that will be effective after the update.
func (u *UpdateIDs) EffectiveIDs(existing []int) []int {
if u == nil {
return nil
}
switch u.Mode {
case RelationshipUpdateModeAdd:
return intslice.IntAppendUniques(existing, u.IDs)
case RelationshipUpdateModeRemove:
return intslice.IntExclude(existing, u.IDs)
case RelationshipUpdateModeSet:
return u.IDs
}
return nil
}
type UpdateStrings struct {
Values []string `json:"values"`
Mode RelationshipUpdateMode `json:"mode"`

92
pkg/models/update_test.go Normal file
View File

@ -0,0 +1,92 @@
package models
import (
"reflect"
"testing"
)
func TestUpdateIDs_ImpactedIDs(t *testing.T) {
tests := []struct {
name string
IDs []int
Mode RelationshipUpdateMode
existing []int
want []int
}{
{
name: "add",
IDs: []int{1, 2, 3},
Mode: RelationshipUpdateModeAdd,
existing: []int{1, 2},
want: []int{3},
},
{
name: "remove",
IDs: []int{1, 2, 3},
Mode: RelationshipUpdateModeRemove,
existing: []int{1, 2},
want: []int{1, 2},
},
{
name: "set",
IDs: []int{1, 2, 3},
Mode: RelationshipUpdateModeSet,
existing: []int{1, 2},
want: []int{3},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u := &UpdateIDs{
IDs: tt.IDs,
Mode: tt.Mode,
}
if got := u.ImpactedIDs(tt.existing); !reflect.DeepEqual(got, tt.want) {
t.Errorf("UpdateIDs.ImpactedIDs() = %v, want %v", got, tt.want)
}
})
}
}
func TestUpdateIDs_EffectiveIDs(t *testing.T) {
tests := []struct {
name string
IDs []int
Mode RelationshipUpdateMode
existing []int
want []int
}{
{
name: "add",
IDs: []int{2, 3},
Mode: RelationshipUpdateModeAdd,
existing: []int{1, 2},
want: []int{1, 2, 3},
},
{
name: "remove",
IDs: []int{2, 3},
Mode: RelationshipUpdateModeRemove,
existing: []int{1, 2},
want: []int{1},
},
{
name: "set",
IDs: []int{1, 2, 3},
Mode: RelationshipUpdateModeSet,
existing: []int{1, 2},
want: []int{1, 2, 3},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
u := &UpdateIDs{
IDs: tt.IDs,
Mode: tt.Mode,
}
if got := u.EffectiveIDs(tt.existing); !reflect.DeepEqual(got, tt.want) {
t.Errorf("UpdateIDs.EffectiveIDs() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -734,6 +734,7 @@ export const mutateAddGalleryImages = (input: GQL.GalleryAddInput) =>
mutation: GQL.AddGalleryImagesDocument,
variables: input,
update: deleteCache(galleryMutationImpactedQueries),
refetchQueries: getQueryNames([GQL.FindGalleryDocument]),
});
export const mutateRemoveGalleryImages = (input: GQL.GalleryRemoveInput) =>
@ -741,6 +742,7 @@ export const mutateRemoveGalleryImages = (input: GQL.GalleryRemoveInput) =>
mutation: GQL.RemoveGalleryImagesDocument,
variables: input,
update: deleteCache(galleryMutationImpactedQueries),
refetchQueries: getQueryNames([GQL.FindGalleryDocument]),
});
export const mutateGallerySetPrimaryFile = (id: string, fileID: string) =>