mirror of https://github.com/stashapp/stash.git
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:
parent
9c8a6ee495
commit
74cef93d19
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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) =>
|
||||
|
|
Loading…
Reference in New Issue