Support for assigning any image from a gallery as the cover (#5053)

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
sezzim
2024-08-28 18:24:52 -07:00
committed by GitHub
parent 8133aa8c91
commit 68738bd227
23 changed files with 383 additions and 10 deletions

View File

@@ -52,6 +52,22 @@ func (s *Service) RemoveImages(ctx context.Context, g *models.Gallery, toRemove
return s.Updated(ctx, g.ID)
}
func (s *Service) SetCover(ctx context.Context, g *models.Gallery, coverImageID int) error {
if err := s.Repository.SetCover(ctx, g.ID, coverImageID); err != nil {
return fmt.Errorf("failed to set cover: %w", err)
}
return s.Updated(ctx, g.ID)
}
func (s *Service) ResetCover(ctx context.Context, g *models.Gallery) error {
if err := s.Repository.ResetCover(ctx, g.ID); err != nil {
return fmt.Errorf("failed to reset cover: %w", err)
}
return s.Updated(ctx, g.ID)
}
func AddPerformer(ctx context.Context, qb models.GalleryUpdater, o *models.Gallery, performerID int) error {
galleryPartial := models.NewGalleryPartial()
galleryPartial.PerformerIDs = &models.UpdateIDs{

View File

@@ -107,6 +107,13 @@ func FindGalleryCover(ctx context.Context, r models.ImageQueryer, galleryID int,
}
func findGalleryCover(ctx context.Context, r models.ImageQueryer, galleryID int, useCoverJpg bool, galleryCoverRegex string) (*models.Image, error) {
img, err := r.CoverByGalleryID(ctx, galleryID)
if err != nil {
return nil, err
} else if img != nil {
return img, nil
}
// try to find cover.jpg in the gallery
perPage := 1
sortBy := "path"

View File

@@ -628,6 +628,34 @@ func (_m *GalleryReaderWriter) RemoveImages(ctx context.Context, galleryID int,
return r0
}
// ResetCover provides a mock function with given fields: ctx, galleryID
func (_m *GalleryReaderWriter) ResetCover(ctx context.Context, galleryID int) error {
ret := _m.Called(ctx, galleryID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int) error); ok {
r0 = rf(ctx, galleryID)
} else {
r0 = ret.Error(0)
}
return r0
}
// SetCover provides a mock function with given fields: ctx, galleryID, coverImageID
func (_m *GalleryReaderWriter) SetCover(ctx context.Context, galleryID int, coverImageID int) error {
ret := _m.Called(ctx, galleryID, coverImageID)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, int, int) error); ok {
r0 = rf(ctx, galleryID, coverImageID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Update provides a mock function with given fields: ctx, updatedGallery
func (_m *GalleryReaderWriter) Update(ctx context.Context, updatedGallery *models.Gallery) error {
ret := _m.Called(ctx, updatedGallery)

View File

@@ -114,6 +114,29 @@ func (_m *ImageReaderWriter) CountByGalleryID(ctx context.Context, galleryID int
return r0, r1
}
// CoverByGalleryID provides a mock function with given fields: ctx, galleryId
func (_m *ImageReaderWriter) CoverByGalleryID(ctx context.Context, galleryId int) (*models.Image, error) {
ret := _m.Called(ctx, galleryId)
var r0 *models.Image
if rf, ok := ret.Get(0).(func(context.Context, int) *models.Image); ok {
r0 = rf(ctx, galleryId)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.Image)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
r1 = rf(ctx, galleryId)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Create provides a mock function with given fields: ctx, newImage, fileIDs
func (_m *ImageReaderWriter) Create(ctx context.Context, newImage *models.Image, fileIDs []models.FileID) error {
ret := _m.Called(ctx, newImage, fileIDs)

View File

@@ -83,6 +83,8 @@ type GalleryWriter interface {
AddFileID(ctx context.Context, id int, fileID FileID) error
AddImages(ctx context.Context, galleryID int, imageIDs ...int) error
RemoveImages(ctx context.Context, galleryID int, imageIDs ...int) error
SetCover(ctx context.Context, galleryID int, coverImageID int) error
ResetCover(ctx context.Context, galleryID int) error
}
// GalleryReaderWriter provides all gallery methods.

View File

@@ -25,6 +25,7 @@ type ImageFinder interface {
type ImageQueryer interface {
Query(ctx context.Context, options ImageQueryOptions) (*ImageQueryResult, error)
QueryCount(ctx context.Context, imageFilter *ImageFilterType, findFilter *FindFilterType) (int, error)
CoverByGalleryID(ctx context.Context, galleryId int) (*Image, error)
}
// ImageCounter provides methods to count images.

View File

@@ -30,7 +30,7 @@ const (
dbConnTimeout = 30
)
var appSchemaVersion uint = 65
var appSchemaVersion uint = 66
//go:embed migrations/*.sql
var migrationsBox embed.FS

View File

@@ -890,6 +890,14 @@ func (qb *GalleryStore) UpdateImages(ctx context.Context, galleryID int, imageID
return galleryRepository.images.replace(ctx, galleryID, imageIDs)
}
func (qb *GalleryStore) SetCover(ctx context.Context, galleryID int, coverImageID int) error {
return imageGalleriesTableMgr.setCover(ctx, coverImageID, galleryID)
}
func (qb *GalleryStore) ResetCover(ctx context.Context, galleryID int) error {
return imageGalleriesTableMgr.resetCover(ctx, galleryID)
}
func (qb *GalleryStore) GetSceneIDs(ctx context.Context, id int) ([]int, error) {
return galleryRepository.scenes.getIDs(ctx, id)
}

View File

@@ -2973,6 +2973,34 @@ func TestGalleryQueryHasChapters(t *testing.T) {
})
}
func TestGallerySetAndResetCover(t *testing.T) {
withTxn(func(ctx context.Context) error {
sqb := db.Gallery
imagePath2 := getFilePath(folderIdxWithImageFiles, getImageBasename(imageIdx2WithGallery))
result, err := db.Image.CoverByGalleryID(ctx, galleryIDs[galleryIdxWithTwoImages])
assert.Nil(t, err)
assert.Nil(t, result)
err = sqb.SetCover(ctx, galleryIDs[galleryIdxWithTwoImages], imageIDs[imageIdx2WithGallery])
assert.Nil(t, err)
result, err = db.Image.CoverByGalleryID(ctx, galleryIDs[galleryIdxWithTwoImages])
assert.Nil(t, err)
assert.Equal(t, result.Path, imagePath2)
err = sqb.ResetCover(ctx, galleryIDs[galleryIdxWithTwoImages])
assert.Nil(t, err)
result, err = db.Image.CoverByGalleryID(ctx, galleryIDs[galleryIdxWithTwoImages])
assert.Nil(t, err)
assert.Nil(t, result)
return nil
})
}
// TODO Count
// TODO All
// TODO Query

View File

@@ -480,6 +480,42 @@ func (qb *ImageStore) getMany(ctx context.Context, q *goqu.SelectDataset) ([]*mo
return ret, nil
}
// Returns the custom cover for the gallery, if one has been set.
func (qb *ImageStore) CoverByGalleryID(ctx context.Context, galleryID int) (*models.Image, error) {
table := qb.table()
sq := dialect.From(table).
InnerJoin(
galleriesImagesJoinTable,
goqu.On(table.Col(idColumn).Eq(galleriesImagesJoinTable.Col(imageIDColumn))),
).
Select(table.Col(idColumn)).
Where(goqu.And(
galleriesImagesJoinTable.Col("gallery_id").Eq(galleryID),
galleriesImagesJoinTable.Col("cover").Eq(true),
))
q := qb.selectDataset().Prepared(true).Where(
table.Col(idColumn).Eq(
sq,
),
)
ret, err := qb.getMany(ctx, q)
if err != nil {
return nil, fmt.Errorf("getting cover for gallery %d: %w", galleryID, err)
}
switch {
case len(ret) > 1:
return nil, fmt.Errorf("internal error: multiple covers returned for gallery %d", galleryID)
case len(ret) == 1:
return ret[0], nil
default:
return nil, nil
}
}
func (qb *ImageStore) GetFiles(ctx context.Context, id int) ([]models.File, error) {
fileIDs, err := imageRepository.files.get(ctx, id)
if err != nil {

View File

@@ -0,0 +1,2 @@
ALTER TABLE `galleries_images` ADD COLUMN `cover` BOOLEAN NOT NULL DEFAULT 0;
CREATE UNIQUE INDEX `index_galleries_images_gallery_id_cover` on `galleries_images` (`gallery_id`, `cover`) WHERE `cover` = 1;

View File

@@ -710,6 +710,45 @@ func (t *scenesGroupsTable) modifyJoins(ctx context.Context, id int, v []models.
return nil
}
type imageGalleriesTable struct {
joinTable
}
func (t *imageGalleriesTable) setCover(ctx context.Context, id int, galleryID int) error {
if err := t.resetCover(ctx, galleryID); err != nil {
return err
}
table := t.table.table
q := dialect.Update(table).Prepared(true).Set(goqu.Record{
"cover": true,
}).Where(t.idColumn.Eq(id), table.Col(galleryIDColumn).Eq(galleryID))
if _, err := exec(ctx, q); err != nil {
return fmt.Errorf("setting cover flag in %s: %w", t.table.table.GetTable(), err)
}
return nil
}
func (t *imageGalleriesTable) resetCover(ctx context.Context, galleryID int) error {
table := t.table.table
q := dialect.Update(table).Prepared(true).Set(goqu.Record{
"cover": false,
}).Where(
table.Col(galleryIDColumn).Eq(galleryID),
table.Col("cover").Eq(true),
)
if _, err := exec(ctx, q); err != nil {
return fmt.Errorf("unsetting cover flags in %s: %w", t.table.table.GetTable(), err)
}
return nil
}
type relatedFilesTable struct {
table
}

View File

@@ -57,12 +57,14 @@ var (
},
}
imageGalleriesTableMgr = &joinTable{
table: table{
table: galleriesImagesJoinTable,
idColumn: galleriesImagesJoinTable.Col(imageIDColumn),
imageGalleriesTableMgr = &imageGalleriesTable{
joinTable: joinTable{
table: table{
table: galleriesImagesJoinTable,
idColumn: galleriesImagesJoinTable.Col(imageIDColumn),
},
fkColumn: galleriesImagesJoinTable.Col(galleryIDColumn),
},
fkColumn: galleriesImagesJoinTable.Col(galleryIDColumn),
}
imagesTagsTableMgr = &joinTable{