mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Decouple galleries from scenes (#1057)
This commit is contained in:
@@ -21,7 +21,7 @@ fragment GallerySlimData on Gallery {
|
|||||||
performers {
|
performers {
|
||||||
...PerformerData
|
...PerformerData
|
||||||
}
|
}
|
||||||
scene {
|
scenes {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
path
|
path
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ fragment GalleryData on Gallery {
|
|||||||
performers {
|
performers {
|
||||||
...PerformerData
|
...PerformerData
|
||||||
}
|
}
|
||||||
scene {
|
scenes {
|
||||||
id
|
...SceneData
|
||||||
title
|
|
||||||
path
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ fragment SlimSceneData on Scene {
|
|||||||
seconds
|
seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
gallery {
|
galleries {
|
||||||
id
|
id
|
||||||
path
|
path
|
||||||
title
|
title
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ fragment SceneData on Scene {
|
|||||||
...SceneMarkerData
|
...SceneMarkerData
|
||||||
}
|
}
|
||||||
|
|
||||||
gallery {
|
galleries {
|
||||||
...GalleryData
|
...GallerySlimData
|
||||||
}
|
}
|
||||||
|
|
||||||
studio {
|
studio {
|
||||||
|
|||||||
@@ -36,14 +36,6 @@ query AllTagsForFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query ValidGalleriesForScene($scene_id: ID!) {
|
|
||||||
validGalleriesForScene(scene_id: $scene_id) {
|
|
||||||
id
|
|
||||||
path
|
|
||||||
title
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query Stats {
|
query Stats {
|
||||||
stats {
|
stats {
|
||||||
scene_count,
|
scene_count,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ query ParseSceneFilenames($filter: FindFilterType!, $config: SceneParserInput!)
|
|||||||
date
|
date
|
||||||
rating
|
rating
|
||||||
studio_id
|
studio_id
|
||||||
gallery_id
|
gallery_ids
|
||||||
movies {
|
movies {
|
||||||
movie_id
|
movie_id
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ type Query {
|
|||||||
|
|
||||||
"""Get marker strings"""
|
"""Get marker strings"""
|
||||||
markerStrings(q: String, sort: String): [MarkerStringsResultType]!
|
markerStrings(q: String, sort: String): [MarkerStringsResultType]!
|
||||||
"""Get the list of valid galleries for a given scene ID"""
|
|
||||||
validGalleriesForScene(scene_id: ID): [Gallery!]!
|
|
||||||
"""Get stats"""
|
"""Get stats"""
|
||||||
stats: StatsResultType!
|
stats: StatsResultType!
|
||||||
"""Organize scene markers by tag for a given scene ID"""
|
"""Organize scene markers by tag for a given scene ID"""
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ type Gallery {
|
|||||||
details: String
|
details: String
|
||||||
rating: Int
|
rating: Int
|
||||||
organized: Boolean!
|
organized: Boolean!
|
||||||
scene: Scene
|
scenes: [Scene!]!
|
||||||
studio: Studio
|
studio: Studio
|
||||||
image_count: Int!
|
image_count: Int!
|
||||||
tags: [Tag!]!
|
tags: [Tag!]!
|
||||||
@@ -33,7 +33,7 @@ input GalleryCreateInput {
|
|||||||
details: String
|
details: String
|
||||||
rating: Int
|
rating: Int
|
||||||
organized: Boolean
|
organized: Boolean
|
||||||
scene_id: ID
|
scene_ids: [ID!]
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
performer_ids: [ID!]
|
performer_ids: [ID!]
|
||||||
@@ -48,7 +48,7 @@ input GalleryUpdateInput {
|
|||||||
details: String
|
details: String
|
||||||
rating: Int
|
rating: Int
|
||||||
organized: Boolean
|
organized: Boolean
|
||||||
scene_id: ID
|
scene_ids: [ID!]
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
performer_ids: [ID!]
|
performer_ids: [ID!]
|
||||||
@@ -62,7 +62,7 @@ input BulkGalleryUpdateInput {
|
|||||||
details: String
|
details: String
|
||||||
rating: Int
|
rating: Int
|
||||||
organized: Boolean
|
organized: Boolean
|
||||||
scene_id: ID
|
scene_ids: BulkUpdateIds
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
tag_ids: BulkUpdateIds
|
tag_ids: BulkUpdateIds
|
||||||
performer_ids: BulkUpdateIds
|
performer_ids: BulkUpdateIds
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type Scene {
|
|||||||
paths: ScenePathsType! # Resolver
|
paths: ScenePathsType! # Resolver
|
||||||
|
|
||||||
scene_markers: [SceneMarker!]!
|
scene_markers: [SceneMarker!]!
|
||||||
gallery: Gallery
|
galleries: [Gallery!]!
|
||||||
studio: Studio
|
studio: Studio
|
||||||
movies: [SceneMovie!]!
|
movies: [SceneMovie!]!
|
||||||
tags: [Tag!]!
|
tags: [Tag!]!
|
||||||
@@ -63,7 +63,7 @@ input SceneUpdateInput {
|
|||||||
rating: Int
|
rating: Int
|
||||||
organized: Boolean
|
organized: Boolean
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
gallery_id: ID
|
gallery_ids: [ID!]
|
||||||
performer_ids: [ID!]
|
performer_ids: [ID!]
|
||||||
movies: [SceneMovieInput!]
|
movies: [SceneMovieInput!]
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
@@ -93,7 +93,7 @@ input BulkSceneUpdateInput {
|
|||||||
rating: Int
|
rating: Int
|
||||||
organized: Boolean
|
organized: Boolean
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
gallery_id: ID
|
gallery_ids: BulkUpdateIds
|
||||||
performer_ids: BulkUpdateIds
|
performer_ids: BulkUpdateIds
|
||||||
tag_ids: BulkUpdateIds
|
tag_ids: BulkUpdateIds
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ type SceneParserResult {
|
|||||||
date: String
|
date: String
|
||||||
rating: Int
|
rating: Int
|
||||||
studio_id: ID
|
studio_id: ID
|
||||||
gallery_id: ID
|
gallery_ids: [ID!]
|
||||||
performer_ids: [ID!]
|
performer_ids: [ID!]
|
||||||
movies: [SceneMovieID!]
|
movies: [SceneMovieID!]
|
||||||
tag_ids: [ID!]
|
tag_ids: [ID!]
|
||||||
|
|||||||
@@ -120,41 +120,6 @@ func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *stri
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) ValidGalleriesForScene(ctx context.Context, scene_id *string) ([]*models.Gallery, error) {
|
|
||||||
if scene_id == nil {
|
|
||||||
panic("nil scene id") // TODO make scene_id mandatory
|
|
||||||
}
|
|
||||||
sceneID, err := strconv.Atoi(*scene_id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var validGalleries []*models.Gallery
|
|
||||||
var sceneGallery *models.Gallery
|
|
||||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
|
||||||
sqb := repo.Scene()
|
|
||||||
scene, err := sqb.Find(sceneID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
qb := repo.Gallery()
|
|
||||||
validGalleries, err = qb.ValidGalleriesForScenePath(scene.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sceneGallery, err = qb.FindBySceneID(sceneID)
|
|
||||||
return err
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sceneGallery != nil {
|
|
||||||
validGalleries = append(validGalleries, sceneGallery)
|
|
||||||
}
|
|
||||||
return validGalleries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, error) {
|
func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, error) {
|
||||||
var ret models.StatsResultType
|
var ret models.StatsResultType
|
||||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||||
|
|||||||
@@ -90,15 +90,10 @@ func (r *galleryResolver) Rating(ctx context.Context, obj *models.Gallery) (*int
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *galleryResolver) Scene(ctx context.Context, obj *models.Gallery) (ret *models.Scene, err error) {
|
func (r *galleryResolver) Scenes(ctx context.Context, obj *models.Gallery) (ret []*models.Scene, err error) {
|
||||||
if !obj.SceneID.Valid {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||||
var err error
|
var err error
|
||||||
ret, err = repo.Scene().Find(int(obj.SceneID.Int64))
|
ret, err = repo.Scene().FindByGalleryID(obj.ID)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ func (r *sceneResolver) SceneMarkers(ctx context.Context, obj *models.Scene) (re
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *sceneResolver) Gallery(ctx context.Context, obj *models.Scene) (ret *models.Gallery, err error) {
|
func (r *sceneResolver) Galleries(ctx context.Context, obj *models.Scene) (ret []*models.Gallery, err error) {
|
||||||
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
|
||||||
ret, err = repo.Gallery().FindBySceneID(obj.ID)
|
ret, err = repo.Gallery().FindBySceneID(obj.ID)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -60,14 +60,6 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input models.Galle
|
|||||||
newGallery.StudioID = sql.NullInt64{Valid: false}
|
newGallery.StudioID = sql.NullInt64{Valid: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.SceneID != nil {
|
|
||||||
sceneID, _ := strconv.ParseInt(*input.SceneID, 10, 64)
|
|
||||||
newGallery.SceneID = sql.NullInt64{Int64: sceneID, Valid: true}
|
|
||||||
} else {
|
|
||||||
// studio must be nullable
|
|
||||||
newGallery.SceneID = sql.NullInt64{Valid: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the transaction and save the gallery
|
// Start the transaction and save the gallery
|
||||||
var gallery *models.Gallery
|
var gallery *models.Gallery
|
||||||
if err := r.withTxn(ctx, func(repo models.Repository) error {
|
if err := r.withTxn(ctx, func(repo models.Repository) error {
|
||||||
@@ -88,6 +80,11 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input models.Galle
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the scenes
|
||||||
|
if err := r.updateGalleryScenes(qb, gallery.ID, input.SceneIds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -112,6 +109,14 @@ func (r *mutationResolver) updateGalleryTags(qb models.GalleryReaderWriter, gall
|
|||||||
return qb.UpdateTags(galleryID, ids)
|
return qb.UpdateTags(galleryID, ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) updateGalleryScenes(qb models.GalleryReaderWriter, galleryID int, sceneIDs []string) error {
|
||||||
|
ids, err := utils.StringSliceToIntSlice(sceneIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return qb.UpdateScenes(galleryID, ids)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) GalleryUpdate(ctx context.Context, input models.GalleryUpdateInput) (ret *models.Gallery, err error) {
|
func (r *mutationResolver) GalleryUpdate(ctx context.Context, input models.GalleryUpdateInput) (ret *models.Gallery, err error) {
|
||||||
translator := changesetTranslator{
|
translator := changesetTranslator{
|
||||||
inputMap: getUpdateInputMap(ctx),
|
inputMap: getUpdateInputMap(ctx),
|
||||||
@@ -221,6 +226,13 @@ func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, transl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the scenes
|
||||||
|
if translator.hasField("scene_ids") {
|
||||||
|
if err := r.updateGalleryScenes(qb, galleryID, input.SceneIds); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return gallery, nil
|
return gallery, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +253,6 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.B
|
|||||||
updatedGallery.Date = translator.sqliteDate(input.Date, "date")
|
updatedGallery.Date = translator.sqliteDate(input.Date, "date")
|
||||||
updatedGallery.Rating = translator.nullInt64(input.Rating, "rating")
|
updatedGallery.Rating = translator.nullInt64(input.Rating, "rating")
|
||||||
updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
|
updatedGallery.StudioID = translator.nullInt64FromString(input.StudioID, "studio_id")
|
||||||
updatedGallery.SceneID = translator.nullInt64FromString(input.SceneID, "scene_id")
|
|
||||||
updatedGallery.Organized = input.Organized
|
updatedGallery.Organized = input.Organized
|
||||||
|
|
||||||
ret := []*models.Gallery{}
|
ret := []*models.Gallery{}
|
||||||
@@ -284,6 +295,18 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.B
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the scenes
|
||||||
|
if translator.hasField("scene_ids") {
|
||||||
|
sceneIDs, err := adjustGallerySceneIDs(qb, galleryID, *input.SceneIds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := qb.UpdateScenes(galleryID, sceneIDs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -312,6 +335,15 @@ func adjustGalleryTagIDs(qb models.GalleryReader, galleryID int, ids models.Bulk
|
|||||||
return adjustIDs(ret, ids), nil
|
return adjustIDs(ret, ids), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func adjustGallerySceneIDs(qb models.GalleryReader, galleryID int, ids models.BulkUpdateIds) (ret []int, err error) {
|
||||||
|
ret, err = qb.GetSceneIDs(galleryID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustIDs(ret, ids), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.GalleryDestroyInput) (bool, error) {
|
func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.GalleryDestroyInput) (bool, error) {
|
||||||
galleryIDs, err := utils.StringSliceToIntSlice(input.Ids)
|
galleryIDs, err := utils.StringSliceToIntSlice(input.Ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -101,29 +101,6 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, translator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the existing gallery value
|
|
||||||
if translator.hasField("gallery_id") {
|
|
||||||
gqb := repo.Gallery()
|
|
||||||
err = gqb.ClearGalleryId(sceneID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if input.GalleryID != nil {
|
|
||||||
// Save the gallery
|
|
||||||
galleryID, _ := strconv.Atoi(*input.GalleryID)
|
|
||||||
updatedGallery := models.GalleryPartial{
|
|
||||||
ID: galleryID,
|
|
||||||
SceneID: &sql.NullInt64{Int64: int64(sceneID), Valid: true},
|
|
||||||
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
|
|
||||||
}
|
|
||||||
_, err := gqb.UpdatePartial(updatedGallery)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the performers
|
// Save the performers
|
||||||
if translator.hasField("performer_ids") {
|
if translator.hasField("performer_ids") {
|
||||||
if err := r.updateScenePerformers(qb, sceneID, input.PerformerIds); err != nil {
|
if err := r.updateScenePerformers(qb, sceneID, input.PerformerIds); err != nil {
|
||||||
@@ -145,6 +122,13 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, translator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the galleries
|
||||||
|
if translator.hasField("gallery_ids") {
|
||||||
|
if err := r.updateSceneGalleries(qb, sceneID, input.GalleryIds); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Save the stash_ids
|
// Save the stash_ids
|
||||||
if translator.hasField("stash_ids") {
|
if translator.hasField("stash_ids") {
|
||||||
stashIDJoins := models.StashIDsFromInput(input.StashIds)
|
stashIDJoins := models.StashIDsFromInput(input.StashIds)
|
||||||
@@ -206,6 +190,14 @@ func (r *mutationResolver) updateSceneTags(qb models.SceneReaderWriter, sceneID
|
|||||||
return qb.UpdateTags(sceneID, ids)
|
return qb.UpdateTags(sceneID, ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) updateSceneGalleries(qb models.SceneReaderWriter, sceneID int, galleryIDs []string) error {
|
||||||
|
ids, err := utils.StringSliceToIntSlice(galleryIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return qb.UpdateGalleries(sceneID, ids)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.BulkSceneUpdateInput) ([]*models.Scene, error) {
|
func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.BulkSceneUpdateInput) ([]*models.Scene, error) {
|
||||||
sceneIDs, err := utils.StringSliceToIntSlice(input.Ids)
|
sceneIDs, err := utils.StringSliceToIntSlice(input.Ids)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -236,7 +228,6 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
|
|||||||
// Start the transaction and save the scene marker
|
// Start the transaction and save the scene marker
|
||||||
if err := r.withTxn(ctx, func(repo models.Repository) error {
|
if err := r.withTxn(ctx, func(repo models.Repository) error {
|
||||||
qb := repo.Scene()
|
qb := repo.Scene()
|
||||||
gqb := repo.Gallery()
|
|
||||||
|
|
||||||
for _, sceneID := range sceneIDs {
|
for _, sceneID := range sceneIDs {
|
||||||
updatedScene.ID = sceneID
|
updatedScene.ID = sceneID
|
||||||
@@ -248,20 +239,6 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
|
|||||||
|
|
||||||
ret = append(ret, scene)
|
ret = append(ret, scene)
|
||||||
|
|
||||||
if translator.hasField("gallery_id") {
|
|
||||||
// Save the gallery
|
|
||||||
galleryID, _ := strconv.Atoi(*input.GalleryID)
|
|
||||||
updatedGallery := models.GalleryPartial{
|
|
||||||
ID: galleryID,
|
|
||||||
SceneID: &sql.NullInt64{Int64: int64(sceneID), Valid: true},
|
|
||||||
UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime},
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := gqb.UpdatePartial(updatedGallery); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the performers
|
// Save the performers
|
||||||
if translator.hasField("performer_ids") {
|
if translator.hasField("performer_ids") {
|
||||||
performerIDs, err := adjustScenePerformerIDs(qb, sceneID, *input.PerformerIds)
|
performerIDs, err := adjustScenePerformerIDs(qb, sceneID, *input.PerformerIds)
|
||||||
@@ -285,6 +262,18 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the galleries
|
||||||
|
if translator.hasField("gallery_ids") {
|
||||||
|
galleryIDs, err := adjustSceneGalleryIDs(qb, sceneID, *input.GalleryIds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := qb.UpdateGalleries(sceneID, galleryIDs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -350,6 +339,15 @@ func adjustSceneTagIDs(qb models.SceneReader, sceneID int, ids models.BulkUpdate
|
|||||||
return adjustIDs(ret, ids), nil
|
return adjustIDs(ret, ids), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func adjustSceneGalleryIDs(qb models.SceneReader, sceneID int, ids models.BulkUpdateIds) (ret []int, err error) {
|
||||||
|
ret, err = qb.GetGalleryIDs(sceneID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustIDs(ret, ids), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneDestroyInput) (bool, error) {
|
func (r *mutationResolver) SceneDestroy(ctx context.Context, input models.SceneDestroyInput) (bool, error) {
|
||||||
sceneID, err := strconv.Atoi(input.ID)
|
sceneID, err := strconv.Atoi(input.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
|
|
||||||
var DB *sqlx.DB
|
var DB *sqlx.DB
|
||||||
var dbPath string
|
var dbPath string
|
||||||
var appSchemaVersion uint = 17
|
var appSchemaVersion uint = 18
|
||||||
var databaseSchemaVersion uint
|
var databaseSchemaVersion uint
|
||||||
|
|
||||||
const sqlite3Driver = "sqlite3ex"
|
const sqlite3Driver = "sqlite3ex"
|
||||||
|
|||||||
138
pkg/database/migrations/18_scene_galleries.up.sql
Normal file
138
pkg/database/migrations/18_scene_galleries.up.sql
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
-- recreate the tables referencing galleries to correct their references
|
||||||
|
ALTER TABLE `galleries` rename to `_galleries_old`;
|
||||||
|
ALTER TABLE `galleries_images` rename to `_galleries_images_old`;
|
||||||
|
ALTER TABLE `galleries_tags` rename to `_galleries_tags_old`;
|
||||||
|
ALTER TABLE `performers_galleries` rename to `_performers_galleries_old`;
|
||||||
|
|
||||||
|
CREATE TABLE `galleries` (
|
||||||
|
`id` integer not null primary key autoincrement,
|
||||||
|
`path` varchar(510),
|
||||||
|
`checksum` varchar(255) not null,
|
||||||
|
`zip` boolean not null default '0',
|
||||||
|
`title` varchar(255),
|
||||||
|
`url` varchar(255),
|
||||||
|
`date` date,
|
||||||
|
`details` text,
|
||||||
|
`studio_id` integer,
|
||||||
|
`rating` tinyint,
|
||||||
|
`file_mod_time` datetime,
|
||||||
|
`organized` boolean not null default '0',
|
||||||
|
`created_at` datetime not null,
|
||||||
|
`updated_at` datetime not null,
|
||||||
|
foreign key(`studio_id`) references `studios`(`id`) on delete SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS `index_galleries_on_scene_id`;
|
||||||
|
DROP INDEX IF EXISTS `galleries_path_unique`;
|
||||||
|
DROP INDEX IF EXISTS `galleries_checksum_unique`;
|
||||||
|
DROP INDEX IF EXISTS `index_galleries_on_studio_id`;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX `galleries_path_unique` on `galleries` (`path`);
|
||||||
|
CREATE UNIQUE INDEX `galleries_checksum_unique` on `galleries` (`checksum`);
|
||||||
|
CREATE INDEX `index_galleries_on_studio_id` on `galleries` (`studio_id`);
|
||||||
|
|
||||||
|
CREATE TABLE `scenes_galleries` (
|
||||||
|
`scene_id` integer,
|
||||||
|
`gallery_id` integer,
|
||||||
|
foreign key(`scene_id`) references `scenes`(`id`) on delete CASCADE,
|
||||||
|
foreign key(`gallery_id`) references `galleries`(`id`) on delete CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX `index_scenes_galleries_on_scene_id` on `scenes_galleries` (`scene_id`);
|
||||||
|
CREATE INDEX `index_scenes_galleries_on_gallery_id` on `scenes_galleries` (`gallery_id`);
|
||||||
|
|
||||||
|
CREATE TABLE `galleries_images` (
|
||||||
|
`gallery_id` integer,
|
||||||
|
`image_id` integer,
|
||||||
|
foreign key(`gallery_id`) references `galleries`(`id`) on delete CASCADE,
|
||||||
|
foreign key(`image_id`) references `images`(`id`) on delete CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS `index_galleries_images_on_image_id`;
|
||||||
|
DROP INDEX IF EXISTS `index_galleries_images_on_gallery_id`;
|
||||||
|
|
||||||
|
CREATE INDEX `index_galleries_images_on_image_id` on `galleries_images` (`image_id`);
|
||||||
|
CREATE INDEX `index_galleries_images_on_gallery_id` on `galleries_images` (`gallery_id`);
|
||||||
|
|
||||||
|
CREATE TABLE `performers_galleries` (
|
||||||
|
`performer_id` integer,
|
||||||
|
`gallery_id` integer,
|
||||||
|
foreign key(`performer_id`) references `performers`(`id`) on delete CASCADE,
|
||||||
|
foreign key(`gallery_id`) references `galleries`(`id`) on delete CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS `index_performers_galleries_on_gallery_id`;
|
||||||
|
DROP INDEX IF EXISTS `index_performers_galleries_on_performer_id`;
|
||||||
|
|
||||||
|
CREATE INDEX `index_performers_galleries_on_gallery_id` on `performers_galleries` (`gallery_id`);
|
||||||
|
CREATE INDEX `index_performers_galleries_on_performer_id` on `performers_galleries` (`performer_id`);
|
||||||
|
|
||||||
|
CREATE TABLE `galleries_tags` (
|
||||||
|
`gallery_id` integer,
|
||||||
|
`tag_id` integer,
|
||||||
|
foreign key(`gallery_id`) references `galleries`(`id`) on delete CASCADE,
|
||||||
|
foreign key(`tag_id`) references `tags`(`id`) on delete CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS `index_galleries_tags_on_tag_id`;
|
||||||
|
DROP INDEX IF EXISTS `index_galleries_tags_on_gallery_id`;
|
||||||
|
|
||||||
|
CREATE INDEX `index_galleries_tags_on_tag_id` on `galleries_tags` (`tag_id`);
|
||||||
|
CREATE INDEX `index_galleries_tags_on_gallery_id` on `galleries_tags` (`gallery_id`);
|
||||||
|
|
||||||
|
-- populate from the old tables
|
||||||
|
INSERT INTO `galleries`
|
||||||
|
(
|
||||||
|
`id`,
|
||||||
|
`path`,
|
||||||
|
`checksum`,
|
||||||
|
`zip`,
|
||||||
|
`title`,
|
||||||
|
`url`,
|
||||||
|
`date`,
|
||||||
|
`details`,
|
||||||
|
`studio_id`,
|
||||||
|
`rating`,
|
||||||
|
`file_mod_time`,
|
||||||
|
`organized`,
|
||||||
|
`created_at`,
|
||||||
|
`updated_at`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
`id`,
|
||||||
|
`path`,
|
||||||
|
`checksum`,
|
||||||
|
`zip`,
|
||||||
|
`title`,
|
||||||
|
`url`,
|
||||||
|
`date`,
|
||||||
|
`details`,
|
||||||
|
`studio_id`,
|
||||||
|
`rating`,
|
||||||
|
`file_mod_time`,
|
||||||
|
`organized`,
|
||||||
|
`created_at`,
|
||||||
|
`updated_at`
|
||||||
|
FROM `_galleries_old`;
|
||||||
|
|
||||||
|
INSERT INTO `scenes_galleries`
|
||||||
|
(
|
||||||
|
`scene_id`,
|
||||||
|
`gallery_id`
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
`scene_id`,
|
||||||
|
`id`
|
||||||
|
FROM `_galleries_old`
|
||||||
|
WHERE scene_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- these tables are a direct copy
|
||||||
|
INSERT INTO `galleries_images` SELECT * from `_galleries_images_old`;
|
||||||
|
INSERT INTO `galleries_tags` SELECT * from `_galleries_tags_old`;
|
||||||
|
INSERT INTO `performers_galleries` SELECT * from `_performers_galleries_old`;
|
||||||
|
|
||||||
|
-- drop old tables
|
||||||
|
DROP TABLE `_galleries_old`;
|
||||||
|
DROP TABLE `_galleries_images_old`;
|
||||||
|
DROP TABLE `_galleries_tags_old`;
|
||||||
|
DROP TABLE `_performers_galleries_old`;
|
||||||
@@ -74,3 +74,14 @@ func GetIDs(galleries []*models.Gallery) []int {
|
|||||||
|
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetChecksums(galleries []*models.Gallery) []string {
|
||||||
|
var results []string
|
||||||
|
for _, gallery := range galleries {
|
||||||
|
if gallery.Checksum != "" {
|
||||||
|
results = append(results, gallery.Checksum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ type Scene struct {
|
|||||||
Organized bool `json:"organized,omitempty"`
|
Organized bool `json:"organized,omitempty"`
|
||||||
OCounter int `json:"o_counter,omitempty"`
|
OCounter int `json:"o_counter,omitempty"`
|
||||||
Details string `json:"details,omitempty"`
|
Details string `json:"details,omitempty"`
|
||||||
Gallery string `json:"gallery,omitempty"`
|
Galleries []string `json:"galleries,omitempty"`
|
||||||
Performers []string `json:"performers,omitempty"`
|
Performers []string `json:"performers,omitempty"`
|
||||||
Movies []SceneMovie `json:"movies,omitempty"`
|
Movies []SceneMovie `json:"movies,omitempty"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
|
|||||||
@@ -17,11 +17,6 @@ import (
|
|||||||
func DestroyScene(scene *models.Scene, repo models.Repository) (func(), error) {
|
func DestroyScene(scene *models.Scene, repo models.Repository) (func(), error) {
|
||||||
qb := repo.Scene()
|
qb := repo.Scene()
|
||||||
mqb := repo.SceneMarker()
|
mqb := repo.SceneMarker()
|
||||||
gqb := repo.Gallery()
|
|
||||||
|
|
||||||
if err := gqb.ClearGalleryId(scene.ID); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
markers, err := mqb.FindBySceneID(scene.ID)
|
markers, err := mqb.FindBySceneID(scene.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -382,15 +382,13 @@ func exportScene(wg *sync.WaitGroup, jobChan <-chan *models.Scene, repo models.R
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneGallery, err := galleryReader.FindBySceneID(s.ID)
|
galleries, err := galleryReader.FindBySceneID(s.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("[scenes] <%s> error getting scene gallery: %s", sceneHash, err.Error())
|
logger.Errorf("[scenes] <%s> error getting scene gallery checksums: %s", sceneHash, err.Error())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if sceneGallery != nil {
|
newSceneJSON.Galleries = gallery.GetChecksums(galleries)
|
||||||
newSceneJSON.Gallery = sceneGallery.Checksum
|
|
||||||
}
|
|
||||||
|
|
||||||
performers, err := performerReader.FindBySceneID(s.ID)
|
performers, err := performerReader.FindBySceneID(s.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -423,9 +421,7 @@ func exportScene(wg *sync.WaitGroup, jobChan <-chan *models.Scene, repo models.R
|
|||||||
t.studios.IDs = utils.IntAppendUnique(t.studios.IDs, int(s.StudioID.Int64))
|
t.studios.IDs = utils.IntAppendUnique(t.studios.IDs, int(s.StudioID.Int64))
|
||||||
}
|
}
|
||||||
|
|
||||||
if sceneGallery != nil {
|
t.galleries.IDs = utils.IntAppendUniques(t.galleries.IDs, gallery.GetIDs(galleries))
|
||||||
t.galleries.IDs = utils.IntAppendUnique(t.galleries.IDs, sceneGallery.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
tagIDs, err := scene.GetDependentTagIDs(tagReader, sceneMarkerReader, s)
|
tagIDs, err := scene.GetDependentTagIDs(tagReader, sceneMarkerReader, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -300,8 +300,6 @@ func (t *ScanTask) associateGallery(wg *sizedwaitgroup.SizedWaitGroup) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// gallery has no SceneID
|
|
||||||
if !g.SceneID.Valid {
|
|
||||||
basename := strings.TrimSuffix(t.FilePath, filepath.Ext(t.FilePath))
|
basename := strings.TrimSuffix(t.FilePath, filepath.Ext(t.FilePath))
|
||||||
var relatedFiles []string
|
var relatedFiles []string
|
||||||
vExt := config.GetVideoExtensions()
|
vExt := config.GetVideoExtensions()
|
||||||
@@ -314,26 +312,13 @@ func (t *ScanTask) associateGallery(wg *sizedwaitgroup.SizedWaitGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, scenePath := range relatedFiles {
|
for _, scenePath := range relatedFiles {
|
||||||
s, err := sqb.FindByPath(scenePath)
|
scene, _ := sqb.FindByPath(scenePath)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// found related Scene
|
// found related Scene
|
||||||
if s != nil {
|
if scene != nil {
|
||||||
logger.Infof("associate: Gallery %s is related to scene: %d", t.FilePath, s.ID)
|
logger.Infof("associate: Gallery %s is related to scene: %d", t.FilePath, scene.ID)
|
||||||
|
|
||||||
g.SceneID.Int64 = int64(s.ID)
|
if err := sqb.UpdateGalleries(scene.ID, []int{g.ID}); err != nil {
|
||||||
g.SceneID.Valid = true
|
return err
|
||||||
|
|
||||||
_, err = qb.Update(*g)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("associate: Error updating gallery sceneId %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// since a gallery can have only one related scene
|
|
||||||
// only first found is associated
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,16 @@ type GalleryReader interface {
|
|||||||
Find(id int) (*Gallery, error)
|
Find(id int) (*Gallery, error)
|
||||||
FindMany(ids []int) ([]*Gallery, error)
|
FindMany(ids []int) ([]*Gallery, error)
|
||||||
FindByChecksum(checksum string) (*Gallery, error)
|
FindByChecksum(checksum string) (*Gallery, error)
|
||||||
|
FindByChecksums(checksums []string) ([]*Gallery, error)
|
||||||
FindByPath(path string) (*Gallery, error)
|
FindByPath(path string) (*Gallery, error)
|
||||||
FindBySceneID(sceneID int) (*Gallery, error)
|
FindBySceneID(sceneID int) ([]*Gallery, error)
|
||||||
FindByImageID(imageID int) ([]*Gallery, error)
|
FindByImageID(imageID int) ([]*Gallery, error)
|
||||||
ValidGalleriesForScenePath(scenePath string) ([]*Gallery, error)
|
|
||||||
Count() (int, error)
|
Count() (int, error)
|
||||||
All() ([]*Gallery, error)
|
All() ([]*Gallery, error)
|
||||||
Query(galleryFilter *GalleryFilterType, findFilter *FindFilterType) ([]*Gallery, int, error)
|
Query(galleryFilter *GalleryFilterType, findFilter *FindFilterType) ([]*Gallery, int, error)
|
||||||
GetPerformerIDs(galleryID int) ([]int, error)
|
GetPerformerIDs(galleryID int) ([]int, error)
|
||||||
GetTagIDs(galleryID int) ([]int, error)
|
GetTagIDs(galleryID int) ([]int, error)
|
||||||
|
GetSceneIDs(galleryID int) ([]int, error)
|
||||||
GetImageIDs(galleryID int) ([]int, error)
|
GetImageIDs(galleryID int) ([]int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,9 +23,9 @@ type GalleryWriter interface {
|
|||||||
UpdatePartial(updatedGallery GalleryPartial) (*Gallery, error)
|
UpdatePartial(updatedGallery GalleryPartial) (*Gallery, error)
|
||||||
UpdateFileModTime(id int, modTime NullSQLiteTimestamp) error
|
UpdateFileModTime(id int, modTime NullSQLiteTimestamp) error
|
||||||
Destroy(id int) error
|
Destroy(id int) error
|
||||||
ClearGalleryId(sceneID int) error
|
|
||||||
UpdatePerformers(galleryID int, performerIDs []int) error
|
UpdatePerformers(galleryID int, performerIDs []int) error
|
||||||
UpdateTags(galleryID int, tagIDs []int) error
|
UpdateTags(galleryID int, tagIDs []int) error
|
||||||
|
UpdateScenes(galleryID int, sceneIDs []int) error
|
||||||
UpdateImages(galleryID int, imageIDs []int) error
|
UpdateImages(galleryID int, imageIDs []int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,20 +35,6 @@ func (_m *GalleryReaderWriter) All() ([]*models.Gallery, error) {
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearGalleryId provides a mock function with given fields: sceneID
|
|
||||||
func (_m *GalleryReaderWriter) ClearGalleryId(sceneID int) error {
|
|
||||||
ret := _m.Called(sceneID)
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(int) error); ok {
|
|
||||||
r0 = rf(sceneID)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count provides a mock function with given fields:
|
// Count provides a mock function with given fields:
|
||||||
func (_m *GalleryReaderWriter) Count() (int, error) {
|
func (_m *GalleryReaderWriter) Count() (int, error) {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
@@ -153,6 +139,29 @@ func (_m *GalleryReaderWriter) FindByChecksum(checksum string) (*models.Gallery,
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindByChecksums provides a mock function with given fields: checksums
|
||||||
|
func (_m *GalleryReaderWriter) FindByChecksums(checksums []string) ([]*models.Gallery, error) {
|
||||||
|
ret := _m.Called(checksums)
|
||||||
|
|
||||||
|
var r0 []*models.Gallery
|
||||||
|
if rf, ok := ret.Get(0).(func([]string) []*models.Gallery); ok {
|
||||||
|
r0 = rf(checksums)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*models.Gallery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func([]string) error); ok {
|
||||||
|
r1 = rf(checksums)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// FindByImageID provides a mock function with given fields: imageID
|
// FindByImageID provides a mock function with given fields: imageID
|
||||||
func (_m *GalleryReaderWriter) FindByImageID(imageID int) ([]*models.Gallery, error) {
|
func (_m *GalleryReaderWriter) FindByImageID(imageID int) ([]*models.Gallery, error) {
|
||||||
ret := _m.Called(imageID)
|
ret := _m.Called(imageID)
|
||||||
@@ -200,15 +209,15 @@ func (_m *GalleryReaderWriter) FindByPath(path string) (*models.Gallery, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindBySceneID provides a mock function with given fields: sceneID
|
// FindBySceneID provides a mock function with given fields: sceneID
|
||||||
func (_m *GalleryReaderWriter) FindBySceneID(sceneID int) (*models.Gallery, error) {
|
func (_m *GalleryReaderWriter) FindBySceneID(sceneID int) ([]*models.Gallery, error) {
|
||||||
ret := _m.Called(sceneID)
|
ret := _m.Called(sceneID)
|
||||||
|
|
||||||
var r0 *models.Gallery
|
var r0 []*models.Gallery
|
||||||
if rf, ok := ret.Get(0).(func(int) *models.Gallery); ok {
|
if rf, ok := ret.Get(0).(func(int) []*models.Gallery); ok {
|
||||||
r0 = rf(sceneID)
|
r0 = rf(sceneID)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).(*models.Gallery)
|
r0 = ret.Get(0).([]*models.Gallery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +323,29 @@ func (_m *GalleryReaderWriter) GetTagIDs(galleryID int) ([]int, error) {
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSceneIDs provides a mock function with given fields: galleryID
|
||||||
|
func (_m *GalleryReaderWriter) GetSceneIDs(galleryID int) ([]int, error) {
|
||||||
|
ret := _m.Called(galleryID)
|
||||||
|
|
||||||
|
var r0 []int
|
||||||
|
if rf, ok := ret.Get(0).(func(int) []int); ok {
|
||||||
|
r0 = rf(galleryID)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||||
|
r1 = rf(galleryID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// Query provides a mock function with given fields: galleryFilter, findFilter
|
// Query provides a mock function with given fields: galleryFilter, findFilter
|
||||||
func (_m *GalleryReaderWriter) Query(galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) ([]*models.Gallery, int, error) {
|
func (_m *GalleryReaderWriter) Query(galleryFilter *models.GalleryFilterType, findFilter *models.FindFilterType) ([]*models.Gallery, int, error) {
|
||||||
ret := _m.Called(galleryFilter, findFilter)
|
ret := _m.Called(galleryFilter, findFilter)
|
||||||
@@ -446,25 +478,16 @@ func (_m *GalleryReaderWriter) UpdateTags(galleryID int, tagIDs []int) error {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidGalleriesForScenePath provides a mock function with given fields: scenePath
|
// UpdateScenes provides a mock function with given fields: galleryID, sceneIDs
|
||||||
func (_m *GalleryReaderWriter) ValidGalleriesForScenePath(scenePath string) ([]*models.Gallery, error) {
|
func (_m *GalleryReaderWriter) UpdateScenes(galleryID int, sceneIDs []int) error {
|
||||||
ret := _m.Called(scenePath)
|
ret := _m.Called(galleryID, sceneIDs)
|
||||||
|
|
||||||
var r0 []*models.Gallery
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string) []*models.Gallery); ok {
|
if rf, ok := ret.Get(0).(func(int, []int) error); ok {
|
||||||
r0 = rf(scenePath)
|
r0 = rf(galleryID, sceneIDs)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
r0 = ret.Error(0)
|
||||||
r0 = ret.Get(0).([]*models.Gallery)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var r1 error
|
return r0
|
||||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
|
||||||
r1 = rf(scenePath)
|
|
||||||
} else {
|
|
||||||
r1 = ret.Error(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0, r1
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -392,6 +392,29 @@ func (_m *SceneReaderWriter) FindByPerformerID(performerID int) ([]*models.Scene
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindByGalleryID provides a mock function with given fields: galleryID
|
||||||
|
func (_m *SceneReaderWriter) FindByGalleryID(galleryID int) ([]*models.Scene, error) {
|
||||||
|
ret := _m.Called(galleryID)
|
||||||
|
|
||||||
|
var r0 []*models.Scene
|
||||||
|
if rf, ok := ret.Get(0).(func(int) []*models.Scene); ok {
|
||||||
|
r0 = rf(galleryID)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*models.Scene)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||||
|
r1 = rf(galleryID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// FindMany provides a mock function with given fields: ids
|
// FindMany provides a mock function with given fields: ids
|
||||||
func (_m *SceneReaderWriter) FindMany(ids []int) ([]*models.Scene, error) {
|
func (_m *SceneReaderWriter) FindMany(ids []int) ([]*models.Scene, error) {
|
||||||
ret := _m.Called(ids)
|
ret := _m.Called(ids)
|
||||||
@@ -461,13 +484,13 @@ func (_m *SceneReaderWriter) GetMovies(sceneID int) ([]models.MoviesScenes, erro
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPerformerIDs provides a mock function with given fields: imageID
|
// GetPerformerIDs provides a mock function with given fields: sceneID
|
||||||
func (_m *SceneReaderWriter) GetPerformerIDs(imageID int) ([]int, error) {
|
func (_m *SceneReaderWriter) GetPerformerIDs(sceneID int) ([]int, error) {
|
||||||
ret := _m.Called(imageID)
|
ret := _m.Called(sceneID)
|
||||||
|
|
||||||
var r0 []int
|
var r0 []int
|
||||||
if rf, ok := ret.Get(0).(func(int) []int); ok {
|
if rf, ok := ret.Get(0).(func(int) []int); ok {
|
||||||
r0 = rf(imageID)
|
r0 = rf(sceneID)
|
||||||
} else {
|
} else {
|
||||||
if ret.Get(0) != nil {
|
if ret.Get(0) != nil {
|
||||||
r0 = ret.Get(0).([]int)
|
r0 = ret.Get(0).([]int)
|
||||||
@@ -476,7 +499,30 @@ func (_m *SceneReaderWriter) GetPerformerIDs(imageID int) ([]int, error) {
|
|||||||
|
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(1).(func(int) error); ok {
|
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||||
r1 = rf(imageID)
|
r1 = rf(sceneID)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGalleryIDs provides a mock function with given fields: sceneID
|
||||||
|
func (_m *SceneReaderWriter) GetGalleryIDs(sceneID int) ([]int, error) {
|
||||||
|
ret := _m.Called(sceneID)
|
||||||
|
|
||||||
|
var r0 []int
|
||||||
|
if rf, ok := ret.Get(0).(func(int) []int); ok {
|
||||||
|
r0 = rf(sceneID)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(int) error); ok {
|
||||||
|
r1 = rf(sceneID)
|
||||||
} else {
|
} else {
|
||||||
r1 = ret.Error(1)
|
r1 = ret.Error(1)
|
||||||
}
|
}
|
||||||
@@ -778,6 +824,20 @@ func (_m *SceneReaderWriter) UpdatePerformers(sceneID int, performerIDs []int) e
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateGalleries provides a mock function with given fields: sceneID, galleryIDs
|
||||||
|
func (_m *SceneReaderWriter) UpdateGalleries(sceneID int, galleryIDs []int) error {
|
||||||
|
ret := _m.Called(sceneID, galleryIDs)
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(int, []int) error); ok {
|
||||||
|
r0 = rf(sceneID, galleryIDs)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateStashIDs provides a mock function with given fields: sceneID, stashIDs
|
// UpdateStashIDs provides a mock function with given fields: sceneID, stashIDs
|
||||||
func (_m *SceneReaderWriter) UpdateStashIDs(sceneID int, stashIDs []models.StashID) error {
|
func (_m *SceneReaderWriter) UpdateStashIDs(sceneID int, stashIDs []models.StashID) error {
|
||||||
ret := _m.Called(sceneID, stashIDs)
|
ret := _m.Called(sceneID, stashIDs)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ type Gallery struct {
|
|||||||
Rating sql.NullInt64 `db:"rating" json:"rating"`
|
Rating sql.NullInt64 `db:"rating" json:"rating"`
|
||||||
Organized bool `db:"organized" json:"organized"`
|
Organized bool `db:"organized" json:"organized"`
|
||||||
StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
|
StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
|
||||||
SceneID sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"`
|
|
||||||
FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
|
FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
|
||||||
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
|
CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"`
|
||||||
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||||
@@ -35,7 +34,6 @@ type GalleryPartial struct {
|
|||||||
Rating *sql.NullInt64 `db:"rating" json:"rating"`
|
Rating *sql.NullInt64 `db:"rating" json:"rating"`
|
||||||
Organized *bool `db:"organized" json:"organized"`
|
Organized *bool `db:"organized" json:"organized"`
|
||||||
StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
|
StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"`
|
||||||
SceneID *sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"`
|
|
||||||
FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
|
FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"`
|
||||||
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
|
CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"`
|
||||||
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"`
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ type SceneReader interface {
|
|||||||
FindByOSHash(oshash string) (*Scene, error)
|
FindByOSHash(oshash string) (*Scene, error)
|
||||||
FindByPath(path string) (*Scene, error)
|
FindByPath(path string) (*Scene, error)
|
||||||
FindByPerformerID(performerID int) ([]*Scene, error)
|
FindByPerformerID(performerID int) ([]*Scene, error)
|
||||||
|
FindByGalleryID(performerID int) ([]*Scene, error)
|
||||||
CountByPerformerID(performerID int) (int, error)
|
CountByPerformerID(performerID int) (int, error)
|
||||||
// FindByStudioID(studioID int) ([]*Scene, error)
|
// FindByStudioID(studioID int) ([]*Scene, error)
|
||||||
FindByMovieID(movieID int) ([]*Scene, error)
|
FindByMovieID(movieID int) ([]*Scene, error)
|
||||||
@@ -25,9 +26,10 @@ type SceneReader interface {
|
|||||||
QueryByPathRegex(findFilter *FindFilterType) ([]*Scene, int, error)
|
QueryByPathRegex(findFilter *FindFilterType) ([]*Scene, int, error)
|
||||||
GetCover(sceneID int) ([]byte, error)
|
GetCover(sceneID int) ([]byte, error)
|
||||||
GetMovies(sceneID int) ([]MoviesScenes, error)
|
GetMovies(sceneID int) ([]MoviesScenes, error)
|
||||||
GetTagIDs(imageID int) ([]int, error)
|
GetTagIDs(sceneID int) ([]int, error)
|
||||||
GetPerformerIDs(imageID int) ([]int, error)
|
GetGalleryIDs(sceneID int) ([]int, error)
|
||||||
GetStashIDs(performerID int) ([]*StashID, error)
|
GetPerformerIDs(sceneID int) ([]int, error)
|
||||||
|
GetStashIDs(sceneID int) ([]*StashID, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SceneWriter interface {
|
type SceneWriter interface {
|
||||||
@@ -43,6 +45,7 @@ type SceneWriter interface {
|
|||||||
DestroyCover(sceneID int) error
|
DestroyCover(sceneID int) error
|
||||||
UpdatePerformers(sceneID int, performerIDs []int) error
|
UpdatePerformers(sceneID int, performerIDs []int) error
|
||||||
UpdateTags(sceneID int, tagIDs []int) error
|
UpdateTags(sceneID int, tagIDs []int) error
|
||||||
|
UpdateGalleries(sceneID int, galleryIDs []int) error
|
||||||
UpdateMovies(sceneID int, movies []MoviesScenes) error
|
UpdateMovies(sceneID int, movies []MoviesScenes) error
|
||||||
UpdateStashIDs(sceneID int, stashIDs []StashID) error
|
UpdateStashIDs(sceneID int, stashIDs []StashID) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,21 +127,6 @@ func GetStudioName(reader models.StudioReader, scene *models.Scene) (string, err
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGalleryChecksum returns the checksum of the provided gallery. It returns an
|
|
||||||
// empty string if there is no gallery assigned to the scene.
|
|
||||||
func GetGalleryChecksum(reader models.GalleryReader, scene *models.Scene) (string, error) {
|
|
||||||
gallery, err := reader.FindBySceneID(scene.ID)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error getting scene gallery: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if gallery != nil {
|
|
||||||
return gallery.Checksum, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTagNames returns a slice of tag names corresponding to the provided
|
// GetTagNames returns a slice of tag names corresponding to the provided
|
||||||
// scene's tags.
|
// scene's tags.
|
||||||
func GetTagNames(reader models.TagReader, scene *models.Scene) ([]string, error) {
|
func GetTagNames(reader models.TagReader, scene *models.Scene) ([]string, error) {
|
||||||
|
|||||||
@@ -307,33 +307,6 @@ var getGalleryChecksumScenarios = []stringTestScenario{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetGalleryChecksum(t *testing.T) {
|
|
||||||
mockGalleryReader := &mocks.GalleryReaderWriter{}
|
|
||||||
|
|
||||||
galleryErr := errors.New("error getting gallery")
|
|
||||||
|
|
||||||
mockGalleryReader.On("FindBySceneID", sceneID).Return(&models.Gallery{
|
|
||||||
Checksum: galleryChecksum,
|
|
||||||
}, nil).Once()
|
|
||||||
mockGalleryReader.On("FindBySceneID", noGalleryID).Return(nil, nil).Once()
|
|
||||||
mockGalleryReader.On("FindBySceneID", errGalleryID).Return(nil, galleryErr).Once()
|
|
||||||
|
|
||||||
for i, s := range getGalleryChecksumScenarios {
|
|
||||||
scene := s.input
|
|
||||||
json, err := GetGalleryChecksum(mockGalleryReader, &scene)
|
|
||||||
|
|
||||||
if !s.err && err != nil {
|
|
||||||
t.Errorf("[%d] unexpected error: %s", i, err.Error())
|
|
||||||
} else if s.err && err == nil {
|
|
||||||
t.Errorf("[%d] expected error not returned", i)
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, s.expected, json, "[%d]", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mockGalleryReader.AssertExpectations(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringSliceTestScenario struct {
|
type stringSliceTestScenario struct {
|
||||||
input models.Scene
|
input models.Scene
|
||||||
expected []string
|
expected []string
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ type Importer struct {
|
|||||||
|
|
||||||
ID int
|
ID int
|
||||||
scene models.Scene
|
scene models.Scene
|
||||||
gallery *models.Gallery
|
galleries []*models.Gallery
|
||||||
performers []*models.Performer
|
performers []*models.Performer
|
||||||
movies []models.MoviesScenes
|
movies []models.MoviesScenes
|
||||||
tags []*models.Tag
|
tags []*models.Tag
|
||||||
@@ -39,7 +39,7 @@ func (i *Importer) PreImport() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := i.populateGallery(); err != nil {
|
if err := i.populateGalleries(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,25 +174,32 @@ func (i *Importer) createStudio(name string) (int, error) {
|
|||||||
return created.ID, nil
|
return created.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Importer) populateGallery() error {
|
func (i *Importer) populateGalleries() error {
|
||||||
if i.Input.Gallery != "" {
|
if len(i.Input.Galleries) > 0 {
|
||||||
gallery, err := i.GalleryWriter.FindByChecksum(i.Input.Gallery)
|
checksums := i.Input.Galleries
|
||||||
|
galleries, err := i.GalleryWriter.FindByChecksums(checksums)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error finding gallery: %s", err.Error())
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if gallery == nil {
|
var pluckedChecksums []string
|
||||||
|
for _, gallery := range galleries {
|
||||||
|
pluckedChecksums = append(pluckedChecksums, gallery.Checksum)
|
||||||
|
}
|
||||||
|
|
||||||
|
missingGalleries := utils.StrFilter(checksums, func(checksum string) bool {
|
||||||
|
return !utils.StrInclude(pluckedChecksums, checksum)
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(missingGalleries) > 0 {
|
||||||
if i.MissingRefBehaviour == models.ImportMissingRefEnumFail {
|
if i.MissingRefBehaviour == models.ImportMissingRefEnumFail {
|
||||||
return fmt.Errorf("scene gallery '%s' not found", i.Input.Studio)
|
return fmt.Errorf("scene galleries [%s] not found", strings.Join(missingGalleries, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't create galleries - just ignore
|
// we don't create galleries - just ignore
|
||||||
if i.MissingRefBehaviour == models.ImportMissingRefEnumIgnore || i.MissingRefBehaviour == models.ImportMissingRefEnumCreate {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i.gallery = gallery
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i.galleries = galleries
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -333,11 +340,14 @@ func (i *Importer) PostImport(id int) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.gallery != nil {
|
if len(i.galleries) > 0 {
|
||||||
i.gallery.SceneID = sql.NullInt64{Int64: int64(id), Valid: true}
|
var galleryIDs []int
|
||||||
_, err := i.GalleryWriter.Update(*i.gallery)
|
for _, gallery := range i.galleries {
|
||||||
if err != nil {
|
galleryIDs = append(galleryIDs, gallery.ID)
|
||||||
return fmt.Errorf("failed to update gallery: %s", err.Error())
|
}
|
||||||
|
|
||||||
|
if err := i.ReaderWriter.UpdateGalleries(id, galleryIDs); err != nil {
|
||||||
|
return fmt.Errorf("failed to associate galleries: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ const (
|
|||||||
missingTagName = "missingTagName"
|
missingTagName = "missingTagName"
|
||||||
|
|
||||||
errPerformersID = 200
|
errPerformersID = 200
|
||||||
|
errGalleriesID = 201
|
||||||
|
|
||||||
missingChecksum = "missingChecksum"
|
missingChecksum = "missingChecksum"
|
||||||
missingOSHash = "missingOSHash"
|
missingOSHash = "missingOSHash"
|
||||||
@@ -164,21 +165,28 @@ func TestImporterPreImportWithGallery(t *testing.T) {
|
|||||||
i := Importer{
|
i := Importer{
|
||||||
GalleryWriter: galleryReaderWriter,
|
GalleryWriter: galleryReaderWriter,
|
||||||
Path: path,
|
Path: path,
|
||||||
|
MissingRefBehaviour: models.ImportMissingRefEnumFail,
|
||||||
Input: jsonschema.Scene{
|
Input: jsonschema.Scene{
|
||||||
Gallery: existingGalleryChecksum,
|
Galleries: []string{
|
||||||
|
existingGalleryChecksum,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryReaderWriter.On("FindByChecksum", existingGalleryChecksum).Return(&models.Gallery{
|
galleryReaderWriter.On("FindByChecksums", []string{existingGalleryChecksum}).Return([]*models.Gallery{
|
||||||
|
{
|
||||||
ID: existingGalleryID,
|
ID: existingGalleryID,
|
||||||
|
Checksum: existingGalleryChecksum,
|
||||||
|
},
|
||||||
}, nil).Once()
|
}, nil).Once()
|
||||||
galleryReaderWriter.On("FindByChecksum", existingGalleryErr).Return(nil, errors.New("FindByChecksum error")).Once()
|
|
||||||
|
galleryReaderWriter.On("FindByChecksums", []string{existingGalleryErr}).Return(nil, errors.New("FindByChecksums error")).Once()
|
||||||
|
|
||||||
err := i.PreImport()
|
err := i.PreImport()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, existingGalleryID, i.gallery.ID)
|
assert.Equal(t, existingGalleryID, i.galleries[0].ID)
|
||||||
|
|
||||||
i.Input.Gallery = existingGalleryErr
|
i.Input.Galleries = []string{existingGalleryErr}
|
||||||
err = i.PreImport()
|
err = i.PreImport()
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
@@ -192,12 +200,14 @@ func TestImporterPreImportWithMissingGallery(t *testing.T) {
|
|||||||
Path: path,
|
Path: path,
|
||||||
GalleryWriter: galleryReaderWriter,
|
GalleryWriter: galleryReaderWriter,
|
||||||
Input: jsonschema.Scene{
|
Input: jsonschema.Scene{
|
||||||
Gallery: missingGalleryChecksum,
|
Galleries: []string{
|
||||||
|
missingGalleryChecksum,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
MissingRefBehaviour: models.ImportMissingRefEnumFail,
|
MissingRefBehaviour: models.ImportMissingRefEnumFail,
|
||||||
}
|
}
|
||||||
|
|
||||||
galleryReaderWriter.On("FindByChecksum", missingGalleryChecksum).Return(nil, nil).Times(3)
|
galleryReaderWriter.On("FindByChecksums", []string{missingGalleryChecksum}).Return(nil, nil).Times(3)
|
||||||
|
|
||||||
err := i.PreImport()
|
err := i.PreImport()
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
@@ -205,12 +215,10 @@ func TestImporterPreImportWithMissingGallery(t *testing.T) {
|
|||||||
i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore
|
i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore
|
||||||
err = i.PreImport()
|
err = i.PreImport()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Nil(t, i.gallery)
|
|
||||||
|
|
||||||
i.MissingRefBehaviour = models.ImportMissingRefEnumCreate
|
i.MissingRefBehaviour = models.ImportMissingRefEnumCreate
|
||||||
err = i.PreImport()
|
err = i.PreImport()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Nil(t, i.gallery)
|
|
||||||
|
|
||||||
galleryReaderWriter.AssertExpectations(t)
|
galleryReaderWriter.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
@@ -506,33 +514,30 @@ func TestImporterPostImport(t *testing.T) {
|
|||||||
readerWriter.AssertExpectations(t)
|
readerWriter.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImporterPostImportUpdateGallery(t *testing.T) {
|
func TestImporterPostImportUpdateGalleries(t *testing.T) {
|
||||||
galleryReaderWriter := &mocks.GalleryReaderWriter{}
|
sceneReaderWriter := &mocks.SceneReaderWriter{}
|
||||||
|
|
||||||
i := Importer{
|
i := Importer{
|
||||||
GalleryWriter: galleryReaderWriter,
|
ReaderWriter: sceneReaderWriter,
|
||||||
gallery: &models.Gallery{
|
galleries: []*models.Gallery{
|
||||||
|
{
|
||||||
ID: existingGalleryID,
|
ID: existingGalleryID,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
updateErr := errors.New("Update error")
|
updateErr := errors.New("UpdateGalleries error")
|
||||||
|
|
||||||
updateArg := *i.gallery
|
sceneReaderWriter.On("UpdateGalleries", sceneID, []int{existingGalleryID}).Return(nil).Once()
|
||||||
updateArg.SceneID = models.NullInt64(sceneID)
|
sceneReaderWriter.On("UpdateGalleries", errGalleriesID, mock.AnythingOfType("[]int")).Return(updateErr).Once()
|
||||||
|
|
||||||
galleryReaderWriter.On("Update", updateArg).Return(nil, nil).Once()
|
|
||||||
|
|
||||||
updateArg.SceneID = models.NullInt64(errGalleryID)
|
|
||||||
galleryReaderWriter.On("Update", updateArg).Return(nil, updateErr).Once()
|
|
||||||
|
|
||||||
err := i.PostImport(sceneID)
|
err := i.PostImport(sceneID)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
err = i.PostImport(errGalleryID)
|
err = i.PostImport(errGalleriesID)
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
galleryReaderWriter.AssertExpectations(t)
|
sceneReaderWriter.AssertExpectations(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImporterPostImportUpdatePerformers(t *testing.T) {
|
func TestImporterPostImportUpdatePerformers(t *testing.T) {
|
||||||
|
|||||||
@@ -83,3 +83,23 @@ func AddTag(qb models.SceneReaderWriter, id int, tagID int) (bool, error) {
|
|||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddGallery(qb models.SceneReaderWriter, id int, galleryID int) (bool, error) {
|
||||||
|
galleryIDs, err := qb.GetGalleryIDs(id)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oldLen := len(galleryIDs)
|
||||||
|
galleryIDs = utils.IntAppendUnique(galleryIDs, galleryID)
|
||||||
|
|
||||||
|
if len(galleryIDs) != oldLen {
|
||||||
|
if err := qb.UpdateGalleries(id, galleryIDs); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -146,34 +146,15 @@ type SceneFragment struct {
|
|||||||
Performers []*PerformerAppearanceFragment "json:\"performers\" graphql:\"performers\""
|
Performers []*PerformerAppearanceFragment "json:\"performers\" graphql:\"performers\""
|
||||||
Fingerprints []*FingerprintFragment "json:\"fingerprints\" graphql:\"fingerprints\""
|
Fingerprints []*FingerprintFragment "json:\"fingerprints\" graphql:\"fingerprints\""
|
||||||
}
|
}
|
||||||
type GalleryFragment struct {
|
|
||||||
ID string "json:\"id\" graphql:\"id\""
|
|
||||||
Title *string "json:\"title\" graphql:\"title\""
|
|
||||||
Details *string "json:\"details\" graphql:\"details\""
|
|
||||||
Duration *int "json:\"duration\" graphql:\"duration\""
|
|
||||||
Date *string "json:\"date\" graphql:\"date\""
|
|
||||||
Urls []*URLFragment "json:\"urls\" graphql:\"urls\""
|
|
||||||
Images []*ImageFragment "json:\"images\" graphql:\"images\""
|
|
||||||
Studio *StudioFragment "json:\"studio\" graphql:\"studio\""
|
|
||||||
Tags []*TagFragment "json:\"tags\" graphql:\"tags\""
|
|
||||||
Performers []*PerformerAppearanceFragment "json:\"performers\" graphql:\"performers\""
|
|
||||||
Fingerprints []*FingerprintFragment "json:\"fingerprints\" graphql:\"fingerprints\""
|
|
||||||
}
|
|
||||||
type FindSceneByFingerprint struct {
|
type FindSceneByFingerprint struct {
|
||||||
FindSceneByFingerprint []*SceneFragment "json:\"findSceneByFingerprint\" graphql:\"findSceneByFingerprint\""
|
FindSceneByFingerprint []*SceneFragment "json:\"findSceneByFingerprint\" graphql:\"findSceneByFingerprint\""
|
||||||
}
|
}
|
||||||
type FindScenesByFingerprints struct {
|
type FindScenesByFingerprints struct {
|
||||||
FindScenesByFingerprints []*SceneFragment "json:\"findScenesByFingerprints\" graphql:\"findScenesByFingerprints\""
|
FindScenesByFingerprints []*SceneFragment "json:\"findScenesByFingerprints\" graphql:\"findScenesByFingerprints\""
|
||||||
}
|
}
|
||||||
type FindGalleriesByFingerprints struct {
|
|
||||||
FindGalleriesByFingerprints []*GalleryFragment `json:"findGalleriesByFingerprints" graphql:"findGalleriesByFingerprints"`
|
|
||||||
}
|
|
||||||
type SearchScene struct {
|
type SearchScene struct {
|
||||||
SearchScene []*SceneFragment "json:\"searchScene\" graphql:\"searchScene\""
|
SearchScene []*SceneFragment "json:\"searchScene\" graphql:\"searchScene\""
|
||||||
}
|
}
|
||||||
type SearchGallery struct {
|
|
||||||
SearchGallery []*GalleryFragment `json:"searchScene" graphql:"searchScene"`
|
|
||||||
}
|
|
||||||
type SubmitFingerprintPayload struct {
|
type SubmitFingerprintPayload struct {
|
||||||
SubmitFingerprint bool "json:\"submitFingerprint\" graphql:\"submitFingerprint\""
|
SubmitFingerprint bool "json:\"submitFingerprint\" graphql:\"submitFingerprint\""
|
||||||
}
|
}
|
||||||
@@ -208,9 +189,15 @@ fragment SceneFragment on Scene {
|
|||||||
... FingerprintFragment
|
... FingerprintFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment TagFragment on Tag {
|
fragment URLFragment on URL {
|
||||||
name
|
url
|
||||||
id
|
type
|
||||||
|
}
|
||||||
|
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||||
|
as
|
||||||
|
performer {
|
||||||
|
... PerformerFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fragment PerformerFragment on Performer {
|
fragment PerformerFragment on Performer {
|
||||||
id
|
id
|
||||||
@@ -245,25 +232,15 @@ fragment PerformerFragment on Performer {
|
|||||||
... BodyModificationFragment
|
... BodyModificationFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment BodyModificationFragment on BodyModification {
|
fragment FuzzyDateFragment on FuzzyDate {
|
||||||
location
|
date
|
||||||
description
|
accuracy
|
||||||
}
|
|
||||||
fragment MeasurementsFragment on Measurements {
|
|
||||||
band_size
|
|
||||||
cup_size
|
|
||||||
waist
|
|
||||||
hip
|
|
||||||
}
|
}
|
||||||
fragment FingerprintFragment on Fingerprint {
|
fragment FingerprintFragment on Fingerprint {
|
||||||
algorithm
|
algorithm
|
||||||
hash
|
hash
|
||||||
duration
|
duration
|
||||||
}
|
}
|
||||||
fragment URLFragment on URL {
|
|
||||||
url
|
|
||||||
type
|
|
||||||
}
|
|
||||||
fragment ImageFragment on Image {
|
fragment ImageFragment on Image {
|
||||||
id
|
id
|
||||||
url
|
url
|
||||||
@@ -280,15 +257,19 @@ fragment StudioFragment on Studio {
|
|||||||
... ImageFragment
|
... ImageFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
fragment TagFragment on Tag {
|
||||||
as
|
name
|
||||||
performer {
|
id
|
||||||
... PerformerFragment
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fragment FuzzyDateFragment on FuzzyDate {
|
fragment MeasurementsFragment on Measurements {
|
||||||
date
|
band_size
|
||||||
accuracy
|
cup_size
|
||||||
|
waist
|
||||||
|
hip
|
||||||
|
}
|
||||||
|
fragment BodyModificationFragment on BodyModification {
|
||||||
|
location
|
||||||
|
description
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -310,61 +291,6 @@ const FindScenesByFingerprintsQuery = `query FindScenesByFingerprints ($fingerpr
|
|||||||
... SceneFragment
|
... SceneFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment ImageFragment on Image {
|
|
||||||
id
|
|
||||||
url
|
|
||||||
width
|
|
||||||
height
|
|
||||||
}
|
|
||||||
fragment StudioFragment on Studio {
|
|
||||||
name
|
|
||||||
id
|
|
||||||
urls {
|
|
||||||
... URLFragment
|
|
||||||
}
|
|
||||||
images {
|
|
||||||
... ImageFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment TagFragment on Tag {
|
|
||||||
name
|
|
||||||
id
|
|
||||||
}
|
|
||||||
fragment MeasurementsFragment on Measurements {
|
|
||||||
band_size
|
|
||||||
cup_size
|
|
||||||
waist
|
|
||||||
hip
|
|
||||||
}
|
|
||||||
fragment BodyModificationFragment on BodyModification {
|
|
||||||
location
|
|
||||||
description
|
|
||||||
}
|
|
||||||
fragment SceneFragment on Scene {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
details
|
|
||||||
duration
|
|
||||||
date
|
|
||||||
urls {
|
|
||||||
... URLFragment
|
|
||||||
}
|
|
||||||
images {
|
|
||||||
... ImageFragment
|
|
||||||
}
|
|
||||||
studio {
|
|
||||||
... StudioFragment
|
|
||||||
}
|
|
||||||
tags {
|
|
||||||
... TagFragment
|
|
||||||
}
|
|
||||||
performers {
|
|
||||||
... PerformerAppearanceFragment
|
|
||||||
}
|
|
||||||
fingerprints {
|
|
||||||
... FingerprintFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||||
as
|
as
|
||||||
performer {
|
performer {
|
||||||
@@ -404,19 +330,74 @@ fragment PerformerFragment on Performer {
|
|||||||
... BodyModificationFragment
|
... BodyModificationFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment FuzzyDateFragment on FuzzyDate {
|
fragment MeasurementsFragment on Measurements {
|
||||||
date
|
band_size
|
||||||
accuracy
|
cup_size
|
||||||
|
waist
|
||||||
|
hip
|
||||||
}
|
}
|
||||||
fragment FingerprintFragment on Fingerprint {
|
fragment FingerprintFragment on Fingerprint {
|
||||||
algorithm
|
algorithm
|
||||||
hash
|
hash
|
||||||
duration
|
duration
|
||||||
}
|
}
|
||||||
|
fragment SceneFragment on Scene {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
details
|
||||||
|
duration
|
||||||
|
date
|
||||||
|
urls {
|
||||||
|
... URLFragment
|
||||||
|
}
|
||||||
|
images {
|
||||||
|
... ImageFragment
|
||||||
|
}
|
||||||
|
studio {
|
||||||
|
... StudioFragment
|
||||||
|
}
|
||||||
|
tags {
|
||||||
|
... TagFragment
|
||||||
|
}
|
||||||
|
performers {
|
||||||
|
... PerformerAppearanceFragment
|
||||||
|
}
|
||||||
|
fingerprints {
|
||||||
|
... FingerprintFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
fragment URLFragment on URL {
|
fragment URLFragment on URL {
|
||||||
url
|
url
|
||||||
type
|
type
|
||||||
}
|
}
|
||||||
|
fragment ImageFragment on Image {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
fragment TagFragment on Tag {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
}
|
||||||
|
fragment StudioFragment on Studio {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
urls {
|
||||||
|
... URLFragment
|
||||||
|
}
|
||||||
|
images {
|
||||||
|
... ImageFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment FuzzyDateFragment on FuzzyDate {
|
||||||
|
date
|
||||||
|
accuracy
|
||||||
|
}
|
||||||
|
fragment BodyModificationFragment on BodyModification {
|
||||||
|
location
|
||||||
|
description
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
func (c *Client) FindScenesByFingerprints(ctx context.Context, fingerprints []string, httpRequestOptions ...client.HTTPRequestOption) (*FindScenesByFingerprints, error) {
|
func (c *Client) FindScenesByFingerprints(ctx context.Context, fingerprints []string, httpRequestOptions ...client.HTTPRequestOption) (*FindScenesByFingerprints, error) {
|
||||||
@@ -437,6 +418,53 @@ const SearchSceneQuery = `query SearchScene ($term: String!) {
|
|||||||
... SceneFragment
|
... SceneFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fragment URLFragment on URL {
|
||||||
|
url
|
||||||
|
type
|
||||||
|
}
|
||||||
|
fragment ImageFragment on Image {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
fragment TagFragment on Tag {
|
||||||
|
name
|
||||||
|
id
|
||||||
|
}
|
||||||
|
fragment PerformerFragment on Performer {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
disambiguation
|
||||||
|
aliases
|
||||||
|
gender
|
||||||
|
urls {
|
||||||
|
... URLFragment
|
||||||
|
}
|
||||||
|
images {
|
||||||
|
... ImageFragment
|
||||||
|
}
|
||||||
|
birthdate {
|
||||||
|
... FuzzyDateFragment
|
||||||
|
}
|
||||||
|
ethnicity
|
||||||
|
country
|
||||||
|
eye_color
|
||||||
|
hair_color
|
||||||
|
height
|
||||||
|
measurements {
|
||||||
|
... MeasurementsFragment
|
||||||
|
}
|
||||||
|
breast_type
|
||||||
|
career_start_year
|
||||||
|
career_end_year
|
||||||
|
tattoos {
|
||||||
|
... BodyModificationFragment
|
||||||
|
}
|
||||||
|
piercings {
|
||||||
|
... BodyModificationFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
fragment FuzzyDateFragment on FuzzyDate {
|
fragment FuzzyDateFragment on FuzzyDate {
|
||||||
date
|
date
|
||||||
accuracy
|
accuracy
|
||||||
@@ -447,11 +475,6 @@ fragment MeasurementsFragment on Measurements {
|
|||||||
waist
|
waist
|
||||||
hip
|
hip
|
||||||
}
|
}
|
||||||
fragment FingerprintFragment on Fingerprint {
|
|
||||||
algorithm
|
|
||||||
hash
|
|
||||||
duration
|
|
||||||
}
|
|
||||||
fragment SceneFragment on Scene {
|
fragment SceneFragment on Scene {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
@@ -477,59 +500,6 @@ fragment SceneFragment on Scene {
|
|||||||
... FingerprintFragment
|
... FingerprintFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fragment TagFragment on Tag {
|
|
||||||
name
|
|
||||||
id
|
|
||||||
}
|
|
||||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
|
||||||
as
|
|
||||||
performer {
|
|
||||||
... PerformerFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment PerformerFragment on Performer {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
disambiguation
|
|
||||||
aliases
|
|
||||||
gender
|
|
||||||
urls {
|
|
||||||
... URLFragment
|
|
||||||
}
|
|
||||||
images {
|
|
||||||
... ImageFragment
|
|
||||||
}
|
|
||||||
birthdate {
|
|
||||||
... FuzzyDateFragment
|
|
||||||
}
|
|
||||||
ethnicity
|
|
||||||
country
|
|
||||||
eye_color
|
|
||||||
hair_color
|
|
||||||
height
|
|
||||||
measurements {
|
|
||||||
... MeasurementsFragment
|
|
||||||
}
|
|
||||||
breast_type
|
|
||||||
career_start_year
|
|
||||||
career_end_year
|
|
||||||
tattoos {
|
|
||||||
... BodyModificationFragment
|
|
||||||
}
|
|
||||||
piercings {
|
|
||||||
... BodyModificationFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment URLFragment on URL {
|
|
||||||
url
|
|
||||||
type
|
|
||||||
}
|
|
||||||
fragment ImageFragment on Image {
|
|
||||||
id
|
|
||||||
url
|
|
||||||
width
|
|
||||||
height
|
|
||||||
}
|
|
||||||
fragment StudioFragment on Studio {
|
fragment StudioFragment on Studio {
|
||||||
name
|
name
|
||||||
id
|
id
|
||||||
@@ -540,137 +510,21 @@ fragment StudioFragment on Studio {
|
|||||||
... ImageFragment
|
... ImageFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fragment PerformerAppearanceFragment on PerformerAppearance {
|
||||||
|
as
|
||||||
|
performer {
|
||||||
|
... PerformerFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
fragment BodyModificationFragment on BodyModification {
|
fragment BodyModificationFragment on BodyModification {
|
||||||
location
|
location
|
||||||
description
|
description
|
||||||
}
|
}
|
||||||
`
|
|
||||||
|
|
||||||
func (c *Client) FindGalleriesByFingerprints(ctx context.Context, fingerprints []string, httpRequestOptions ...client.HTTPRequestOption) (*FindGalleriesByFingerprints, error) {
|
|
||||||
vars := map[string]interface{}{
|
|
||||||
"fingerprints": fingerprints,
|
|
||||||
}
|
|
||||||
|
|
||||||
var res FindGalleriesByFingerprints
|
|
||||||
if err := c.Client.Post(ctx, FindScenesByFingerprintsQuery, &res, vars, httpRequestOptions...); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const SearchGalleryQuery = `query SearchGallery ($term: String!) {
|
|
||||||
searchGallery(term: $term) {
|
|
||||||
... GalleryFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment FuzzyDateFragment on FuzzyDate {
|
|
||||||
date
|
|
||||||
accuracy
|
|
||||||
}
|
|
||||||
fragment MeasurementsFragment on Measurements {
|
|
||||||
band_size
|
|
||||||
cup_size
|
|
||||||
waist
|
|
||||||
hip
|
|
||||||
}
|
|
||||||
fragment FingerprintFragment on Fingerprint {
|
fragment FingerprintFragment on Fingerprint {
|
||||||
algorithm
|
algorithm
|
||||||
hash
|
hash
|
||||||
duration
|
duration
|
||||||
}
|
}
|
||||||
fragment GalleryFragment on Gallery {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
details
|
|
||||||
duration
|
|
||||||
date
|
|
||||||
urls {
|
|
||||||
... URLFragment
|
|
||||||
}
|
|
||||||
images {
|
|
||||||
... ImageFragment
|
|
||||||
}
|
|
||||||
studio {
|
|
||||||
... StudioFragment
|
|
||||||
}
|
|
||||||
tags {
|
|
||||||
... TagFragment
|
|
||||||
}
|
|
||||||
performers {
|
|
||||||
... PerformerAppearanceFragment
|
|
||||||
}
|
|
||||||
fingerprints {
|
|
||||||
... FingerprintFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment TagFragment on Tag {
|
|
||||||
name
|
|
||||||
id
|
|
||||||
}
|
|
||||||
fragment PerformerAppearanceFragment on PerformerAppearance {
|
|
||||||
as
|
|
||||||
performer {
|
|
||||||
... PerformerFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment PerformerFragment on Performer {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
disambiguation
|
|
||||||
aliases
|
|
||||||
gender
|
|
||||||
urls {
|
|
||||||
... URLFragment
|
|
||||||
}
|
|
||||||
images {
|
|
||||||
... ImageFragment
|
|
||||||
}
|
|
||||||
birthdate {
|
|
||||||
... FuzzyDateFragment
|
|
||||||
}
|
|
||||||
ethnicity
|
|
||||||
country
|
|
||||||
eye_color
|
|
||||||
hair_color
|
|
||||||
height
|
|
||||||
measurements {
|
|
||||||
... MeasurementsFragment
|
|
||||||
}
|
|
||||||
breast_type
|
|
||||||
career_start_year
|
|
||||||
career_end_year
|
|
||||||
tattoos {
|
|
||||||
... BodyModificationFragment
|
|
||||||
}
|
|
||||||
piercings {
|
|
||||||
... BodyModificationFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment URLFragment on URL {
|
|
||||||
url
|
|
||||||
type
|
|
||||||
}
|
|
||||||
fragment ImageFragment on Image {
|
|
||||||
id
|
|
||||||
url
|
|
||||||
width
|
|
||||||
height
|
|
||||||
}
|
|
||||||
fragment StudioFragment on Studio {
|
|
||||||
name
|
|
||||||
id
|
|
||||||
urls {
|
|
||||||
... URLFragment
|
|
||||||
}
|
|
||||||
images {
|
|
||||||
... ImageFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment BodyModificationFragment on BodyModification {
|
|
||||||
location
|
|
||||||
description
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
|
|
||||||
func (c *Client) SearchScene(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchScene, error) {
|
func (c *Client) SearchScene(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchScene, error) {
|
||||||
@@ -686,19 +540,6 @@ func (c *Client) SearchScene(ctx context.Context, term string, httpRequestOption
|
|||||||
return &res, nil
|
return &res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SearchGallery(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchGallery, error) {
|
|
||||||
vars := map[string]interface{}{
|
|
||||||
"term": term,
|
|
||||||
}
|
|
||||||
|
|
||||||
var res SearchGallery
|
|
||||||
if err := c.Client.Post(ctx, SearchGalleryQuery, &res, vars, httpRequestOptions...); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const SubmitFingerprintQuery = `mutation SubmitFingerprint ($input: FingerprintSubmission!) {
|
const SubmitFingerprintQuery = `mutation SubmitFingerprint ($input: FingerprintSubmission!) {
|
||||||
submitFingerprint(input: $input)
|
submitFingerprint(input: $input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package sqlite
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
@@ -14,6 +13,7 @@ const galleryTable = "galleries"
|
|||||||
const performersGalleriesTable = "performers_galleries"
|
const performersGalleriesTable = "performers_galleries"
|
||||||
const galleriesTagsTable = "galleries_tags"
|
const galleriesTagsTable = "galleries_tags"
|
||||||
const galleriesImagesTable = "galleries_images"
|
const galleriesImagesTable = "galleries_images"
|
||||||
|
const galleriesScenesTable = "scenes_galleries"
|
||||||
const galleryIDColumn = "gallery_id"
|
const galleryIDColumn = "gallery_id"
|
||||||
|
|
||||||
type galleryQueryBuilder struct {
|
type galleryQueryBuilder struct {
|
||||||
@@ -73,23 +73,6 @@ func (qb *galleryQueryBuilder) Destroy(id int) error {
|
|||||||
return qb.destroyExisting([]int{id})
|
return qb.destroyExisting([]int{id})
|
||||||
}
|
}
|
||||||
|
|
||||||
type GalleryNullSceneID struct {
|
|
||||||
SceneID sql.NullInt64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qb *galleryQueryBuilder) ClearGalleryId(sceneID int) error {
|
|
||||||
_, err := qb.tx.NamedExec(
|
|
||||||
`UPDATE galleries SET scene_id = null WHERE scene_id = :sceneid`,
|
|
||||||
GalleryNullSceneID{
|
|
||||||
SceneID: sql.NullInt64{
|
|
||||||
Int64: int64(sceneID),
|
|
||||||
Valid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (qb *galleryQueryBuilder) Find(id int) (*models.Gallery, error) {
|
func (qb *galleryQueryBuilder) Find(id int) (*models.Gallery, error) {
|
||||||
var ret models.Gallery
|
var ret models.Gallery
|
||||||
if err := qb.get(id, &ret); err != nil {
|
if err := qb.get(id, &ret); err != nil {
|
||||||
@@ -125,22 +108,29 @@ func (qb *galleryQueryBuilder) FindByChecksum(checksum string) (*models.Gallery,
|
|||||||
return qb.queryGallery(query, args)
|
return qb.queryGallery(query, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qb *galleryQueryBuilder) FindByChecksums(checksums []string) ([]*models.Gallery, error) {
|
||||||
|
query := "SELECT * FROM galleries WHERE checksum IN " + getInBinding(len(checksums))
|
||||||
|
var args []interface{}
|
||||||
|
for _, checksum := range checksums {
|
||||||
|
args = append(args, checksum)
|
||||||
|
}
|
||||||
|
return qb.queryGalleries(query, args)
|
||||||
|
}
|
||||||
|
|
||||||
func (qb *galleryQueryBuilder) FindByPath(path string) (*models.Gallery, error) {
|
func (qb *galleryQueryBuilder) FindByPath(path string) (*models.Gallery, error) {
|
||||||
query := "SELECT * FROM galleries WHERE path = ? LIMIT 1"
|
query := "SELECT * FROM galleries WHERE path = ? LIMIT 1"
|
||||||
args := []interface{}{path}
|
args := []interface{}{path}
|
||||||
return qb.queryGallery(query, args)
|
return qb.queryGallery(query, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *galleryQueryBuilder) FindBySceneID(sceneID int) (*models.Gallery, error) {
|
func (qb *galleryQueryBuilder) FindBySceneID(sceneID int) ([]*models.Gallery, error) {
|
||||||
query := "SELECT galleries.* FROM galleries WHERE galleries.scene_id = ? LIMIT 1"
|
query := selectAll(galleryTable) + `
|
||||||
|
LEFT JOIN scenes_galleries as scenes_join on scenes_join.gallery_id = galleries.id
|
||||||
|
WHERE scenes_join.scene_id = ?
|
||||||
|
GROUP BY galleries.id
|
||||||
|
`
|
||||||
args := []interface{}{sceneID}
|
args := []interface{}{sceneID}
|
||||||
return qb.queryGallery(query, args)
|
return qb.queryGalleries(query, args)
|
||||||
}
|
|
||||||
|
|
||||||
func (qb *galleryQueryBuilder) ValidGalleriesForScenePath(scenePath string) ([]*models.Gallery, error) {
|
|
||||||
sceneDirPath := filepath.Dir(scenePath)
|
|
||||||
query := "SELECT galleries.* FROM galleries WHERE galleries.scene_id IS NULL AND galleries.path LIKE '" + sceneDirPath + "%' ORDER BY path ASC"
|
|
||||||
return qb.queryGalleries(query, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *galleryQueryBuilder) FindByImageID(imageID int) ([]*models.Gallery, error) {
|
func (qb *galleryQueryBuilder) FindByImageID(imageID int) ([]*models.Gallery, error) {
|
||||||
@@ -182,6 +172,7 @@ func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, fi
|
|||||||
query.body = selectDistinctIDs("galleries")
|
query.body = selectDistinctIDs("galleries")
|
||||||
query.body += `
|
query.body += `
|
||||||
left join performers_galleries as performers_join on performers_join.gallery_id = galleries.id
|
left join performers_galleries as performers_join on performers_join.gallery_id = galleries.id
|
||||||
|
left join scenes_galleries as scenes_join on scenes_join.gallery_id = galleries.id
|
||||||
left join studios as studio on studio.id = galleries.studio_id
|
left join studios as studio on studio.id = galleries.studio_id
|
||||||
left join galleries_tags as tags_join on tags_join.gallery_id = galleries.id
|
left join galleries_tags as tags_join on tags_join.gallery_id = galleries.id
|
||||||
left join galleries_images as images_join on images_join.gallery_id = galleries.id
|
left join galleries_images as images_join on images_join.gallery_id = galleries.id
|
||||||
@@ -189,7 +180,7 @@ func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, fi
|
|||||||
`
|
`
|
||||||
|
|
||||||
if q := findFilter.Q; q != nil && *q != "" {
|
if q := findFilter.Q; q != nil && *q != "" {
|
||||||
searchColumns := []string{"galleries.path", "galleries.checksum"}
|
searchColumns := []string{"galleries.title", "galleries.path", "galleries.checksum"}
|
||||||
clause, thisArgs := getSearchBinding(searchColumns, *q, false)
|
clause, thisArgs := getSearchBinding(searchColumns, *q, false)
|
||||||
query.addWhere(clause)
|
query.addWhere(clause)
|
||||||
query.addArg(thisArgs...)
|
query.addArg(thisArgs...)
|
||||||
@@ -221,8 +212,8 @@ func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, fi
|
|||||||
|
|
||||||
if isMissingFilter := galleryFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
|
if isMissingFilter := galleryFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
|
||||||
switch *isMissingFilter {
|
switch *isMissingFilter {
|
||||||
case "scene":
|
case "scenes":
|
||||||
query.addWhere("galleries.scene_id IS NULL")
|
query.addWhere("scenes_join.gallery_id IS NULL")
|
||||||
case "studio":
|
case "studio":
|
||||||
query.addWhere("galleries.studio_id IS NULL")
|
query.addWhere("galleries.studio_id IS NULL")
|
||||||
case "performers":
|
case "performers":
|
||||||
@@ -442,3 +433,23 @@ func (qb *galleryQueryBuilder) UpdateImages(galleryID int, imageIDs []int) error
|
|||||||
// Delete the existing joins and then create new ones
|
// Delete the existing joins and then create new ones
|
||||||
return qb.imagesRepository().replace(galleryID, imageIDs)
|
return qb.imagesRepository().replace(galleryID, imageIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qb *galleryQueryBuilder) scenesRepository() *joinRepository {
|
||||||
|
return &joinRepository{
|
||||||
|
repository: repository{
|
||||||
|
tx: qb.tx,
|
||||||
|
tableName: galleriesScenesTable,
|
||||||
|
idColumn: galleryIDColumn,
|
||||||
|
},
|
||||||
|
fkColumn: sceneIDColumn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qb *galleryQueryBuilder) GetSceneIDs(galleryID int) ([]int, error) {
|
||||||
|
return qb.scenesRepository().getIDs(galleryID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qb *galleryQueryBuilder) UpdateScenes(galleryID int, sceneIDs []int) error {
|
||||||
|
// Delete the existing joins and then create new ones
|
||||||
|
return qb.scenesRepository().replace(galleryID, sceneIDs)
|
||||||
|
}
|
||||||
|
|||||||
@@ -94,21 +94,21 @@ func TestGalleryFindBySceneID(t *testing.T) {
|
|||||||
gqb := r.Gallery()
|
gqb := r.Gallery()
|
||||||
|
|
||||||
sceneID := sceneIDs[sceneIdxWithGallery]
|
sceneID := sceneIDs[sceneIdxWithGallery]
|
||||||
gallery, err := gqb.FindBySceneID(sceneID)
|
galleries, err := gqb.FindBySceneID(sceneID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error finding gallery: %s", err.Error())
|
t.Errorf("Error finding gallery: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, getGalleryStringValue(galleryIdxWithScene, "Path"), gallery.Path.String)
|
assert.Equal(t, getGalleryStringValue(galleryIdxWithScene, "Path"), galleries[0].Path.String)
|
||||||
|
|
||||||
gallery, err = gqb.FindBySceneID(0)
|
galleries, err = gqb.FindBySceneID(0)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error finding gallery: %s", err.Error())
|
t.Errorf("Error finding gallery: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Nil(t, gallery)
|
assert.Nil(t, galleries)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -233,7 +233,7 @@ func verifyGalleriesRating(t *testing.T, ratingCriterion models.IntCriterionInpu
|
|||||||
func TestGalleryQueryIsMissingScene(t *testing.T) {
|
func TestGalleryQueryIsMissingScene(t *testing.T) {
|
||||||
withTxn(func(r models.Repository) error {
|
withTxn(func(r models.Repository) error {
|
||||||
qb := r.Gallery()
|
qb := r.Gallery()
|
||||||
isMissing := "scene"
|
isMissing := "scenes"
|
||||||
galleryFilter := models.GalleryFilterType{
|
galleryFilter := models.GalleryFilterType{
|
||||||
IsMissing: &isMissing,
|
IsMissing: &isMissing,
|
||||||
}
|
}
|
||||||
@@ -265,10 +265,8 @@ func TestGalleryQueryIsMissingScene(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO ValidGalleriesForScenePath
|
|
||||||
// TODO Count
|
// TODO Count
|
||||||
// TODO All
|
// TODO All
|
||||||
// TODO Query
|
// TODO Query
|
||||||
// TODO Update
|
// TODO Update
|
||||||
// TODO Destroy
|
// TODO Destroy
|
||||||
// TODO ClearGalleryId
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const sceneTable = "scenes"
|
|||||||
const sceneIDColumn = "scene_id"
|
const sceneIDColumn = "scene_id"
|
||||||
const performersScenesTable = "performers_scenes"
|
const performersScenesTable = "performers_scenes"
|
||||||
const scenesTagsTable = "scenes_tags"
|
const scenesTagsTable = "scenes_tags"
|
||||||
|
const scenesGalleriesTable = "scenes_galleries"
|
||||||
const moviesScenesTable = "movies_scenes"
|
const moviesScenesTable = "movies_scenes"
|
||||||
|
|
||||||
var scenesForPerformerQuery = selectAll(sceneTable) + `
|
var scenesForPerformerQuery = selectAll(sceneTable) + `
|
||||||
@@ -44,6 +45,12 @@ WHERE scenes_tags.tag_id = ?
|
|||||||
GROUP BY scenes_tags.scene_id
|
GROUP BY scenes_tags.scene_id
|
||||||
`
|
`
|
||||||
|
|
||||||
|
var scenesForGalleryQuery = selectAll(sceneTable) + `
|
||||||
|
LEFT JOIN scenes_galleries as galleries_join on galleries_join.scene_id = scenes.id
|
||||||
|
WHERE galleries_join.gallery_id = ?
|
||||||
|
GROUP BY scenes.id
|
||||||
|
`
|
||||||
|
|
||||||
var countScenesForMissingChecksumQuery = `
|
var countScenesForMissingChecksumQuery = `
|
||||||
SELECT id FROM scenes
|
SELECT id FROM scenes
|
||||||
WHERE scenes.checksum is null
|
WHERE scenes.checksum is null
|
||||||
@@ -221,6 +228,11 @@ func (qb *sceneQueryBuilder) FindByPerformerID(performerID int) ([]*models.Scene
|
|||||||
return qb.queryScenes(scenesForPerformerQuery, args)
|
return qb.queryScenes(scenesForPerformerQuery, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qb *sceneQueryBuilder) FindByGalleryID(galleryID int) ([]*models.Scene, error) {
|
||||||
|
args := []interface{}{galleryID}
|
||||||
|
return qb.queryScenes(scenesForGalleryQuery, args)
|
||||||
|
}
|
||||||
|
|
||||||
func (qb *sceneQueryBuilder) CountByPerformerID(performerID int) (int, error) {
|
func (qb *sceneQueryBuilder) CountByPerformerID(performerID int) (int, error) {
|
||||||
args := []interface{}{performerID}
|
args := []interface{}{performerID}
|
||||||
return qb.runCountQuery(qb.buildCountQuery(countScenesForPerformerQuery), args)
|
return qb.runCountQuery(qb.buildCountQuery(countScenesForPerformerQuery), args)
|
||||||
@@ -293,7 +305,7 @@ func (qb *sceneQueryBuilder) Query(sceneFilter *models.SceneFilterType, findFilt
|
|||||||
left join performers_scenes as performers_join on performers_join.scene_id = scenes.id
|
left join performers_scenes as performers_join on performers_join.scene_id = scenes.id
|
||||||
left join movies_scenes as movies_join on movies_join.scene_id = scenes.id
|
left join movies_scenes as movies_join on movies_join.scene_id = scenes.id
|
||||||
left join studios as studio on studio.id = scenes.studio_id
|
left join studios as studio on studio.id = scenes.studio_id
|
||||||
left join galleries as gallery on gallery.scene_id = scenes.id
|
left join scenes_galleries as galleries_join on galleries_join.scene_id = scenes.id
|
||||||
left join scenes_tags as tags_join on tags_join.scene_id = scenes.id
|
left join scenes_tags as tags_join on tags_join.scene_id = scenes.id
|
||||||
left join scene_stash_ids on scene_stash_ids.scene_id = scenes.id
|
left join scene_stash_ids on scene_stash_ids.scene_id = scenes.id
|
||||||
`
|
`
|
||||||
@@ -368,8 +380,8 @@ func (qb *sceneQueryBuilder) Query(sceneFilter *models.SceneFilterType, findFilt
|
|||||||
|
|
||||||
if isMissingFilter := sceneFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
|
if isMissingFilter := sceneFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" {
|
||||||
switch *isMissingFilter {
|
switch *isMissingFilter {
|
||||||
case "gallery":
|
case "galleries":
|
||||||
query.addWhere("gallery.scene_id IS NULL")
|
query.addWhere("galleries_join.scene_id IS NULL")
|
||||||
case "studio":
|
case "studio":
|
||||||
query.addWhere("scenes.studio_id IS NULL")
|
query.addWhere("scenes.studio_id IS NULL")
|
||||||
case "movie":
|
case "movie":
|
||||||
@@ -683,6 +695,26 @@ func (qb *sceneQueryBuilder) UpdateTags(id int, tagIDs []int) error {
|
|||||||
return qb.tagsRepository().replace(id, tagIDs)
|
return qb.tagsRepository().replace(id, tagIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (qb *sceneQueryBuilder) galleriesRepository() *joinRepository {
|
||||||
|
return &joinRepository{
|
||||||
|
repository: repository{
|
||||||
|
tx: qb.tx,
|
||||||
|
tableName: scenesGalleriesTable,
|
||||||
|
idColumn: sceneIDColumn,
|
||||||
|
},
|
||||||
|
fkColumn: galleryIDColumn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qb *sceneQueryBuilder) GetGalleryIDs(id int) ([]int, error) {
|
||||||
|
return qb.galleriesRepository().getIDs(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (qb *sceneQueryBuilder) UpdateGalleries(id int, galleryIDs []int) error {
|
||||||
|
// Delete the existing joins and then create new ones
|
||||||
|
return qb.galleriesRepository().replace(id, galleryIDs)
|
||||||
|
}
|
||||||
|
|
||||||
func (qb *sceneQueryBuilder) stashIDRepository() *stashIDRepository {
|
func (qb *sceneQueryBuilder) stashIDRepository() *stashIDRepository {
|
||||||
return &stashIDRepository{
|
return &stashIDRepository{
|
||||||
repository{
|
repository{
|
||||||
|
|||||||
@@ -494,7 +494,7 @@ func TestSceneQueryHasMarkers(t *testing.T) {
|
|||||||
func TestSceneQueryIsMissingGallery(t *testing.T) {
|
func TestSceneQueryIsMissingGallery(t *testing.T) {
|
||||||
withTxn(func(r models.Repository) error {
|
withTxn(func(r models.Repository) error {
|
||||||
sqb := r.Scene()
|
sqb := r.Scene()
|
||||||
isMissing := "gallery"
|
isMissing := "galleries"
|
||||||
sceneFilter := models.SceneFilterType{
|
sceneFilter := models.SceneFilterType{
|
||||||
IsMissing: &isMissing,
|
IsMissing: &isMissing,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package sqlite_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -192,7 +191,7 @@ func populateDB() error {
|
|||||||
return fmt.Errorf("error creating studios: %s", err.Error())
|
return fmt.Errorf("error creating studios: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := linkSceneGallery(r.Gallery(), sceneIdxWithGallery, galleryIdxWithScene); err != nil {
|
if err := linkSceneGallery(r.Scene(), sceneIdxWithGallery, galleryIdxWithScene); err != nil {
|
||||||
return fmt.Errorf("error linking scene to gallery: %s", err.Error())
|
return fmt.Errorf("error linking scene to gallery: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,20 +622,8 @@ func linkScenePerformer(qb models.SceneReaderWriter, sceneIndex, performerIndex
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func linkSceneGallery(gqb models.GalleryReaderWriter, sceneIndex, galleryIndex int) error {
|
func linkSceneGallery(qb models.SceneReaderWriter, sceneIndex, galleryIndex int) error {
|
||||||
gallery, err := gqb.Find(galleryIDs[galleryIndex])
|
_, err := scene.AddGallery(qb, sceneIDs[sceneIndex], galleryIDs[galleryIndex])
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error finding gallery: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if gallery == nil {
|
|
||||||
return errors.New("gallery is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
gallery.SceneID = sql.NullInt64{Int64: int64(sceneIDs[sceneIndex]), Valid: true}
|
|
||||||
_, err = gqb.Update(*gallery)
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#### 💥 Note: After upgrading, all scene file sizes will be 0B until a new [scan](/settings?tab=tasks) is run.
|
#### 💥 Note: After upgrading, all scene file sizes will be 0B until a new [scan](/settings?tab=tasks) is run.
|
||||||
|
|
||||||
### ✨ New Features
|
### ✨ New Features
|
||||||
|
* Add support for multiple galleries per scene, and vice-versa.
|
||||||
* Add backup database functionality to Settings/Tasks.
|
* Add backup database functionality to Settings/Tasks.
|
||||||
* Add gallery wall view.
|
* Add gallery wall view.
|
||||||
* Add organized flag for scenes, galleries and images.
|
* Add organized flag for scenes, galleries and images.
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useToast } from "src/hooks";
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
interface IDeleteGalleryDialogProps {
|
interface IDeleteGalleryDialogProps {
|
||||||
selected: Partial<GQL.GalleryDataFragment>[];
|
selected: Pick<GQL.Gallery, "id">[];
|
||||||
onClose: (confirmed: boolean) => void;
|
onClose: (confirmed: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import { TextUtils } from "src/utils";
|
|||||||
interface IProps {
|
interface IProps {
|
||||||
gallery: GQL.GallerySlimDataFragment;
|
gallery: GQL.GallerySlimDataFragment;
|
||||||
selecting?: boolean;
|
selecting?: boolean;
|
||||||
selected: boolean | undefined;
|
selected?: boolean | undefined;
|
||||||
zoomIndex: number;
|
zoomIndex?: number;
|
||||||
onSelectedChanged: (selected: boolean, shiftKey: boolean) => void;
|
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GalleryCard: React.FC<IProps> = (props) => {
|
export const GalleryCard: React.FC<IProps> = (props) => {
|
||||||
@@ -27,19 +27,18 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
|||||||
config?.data?.configuration.interface.showStudioAsText ?? false;
|
config?.data?.configuration.interface.showStudioAsText ?? false;
|
||||||
|
|
||||||
function maybeRenderScenePopoverButton() {
|
function maybeRenderScenePopoverButton() {
|
||||||
if (!props.gallery.scene) return;
|
if (props.gallery.scenes.length === 0) return;
|
||||||
|
|
||||||
const popoverContent = (
|
const popoverContent = props.gallery.scenes.map((scene) => (
|
||||||
<TagLink key={props.gallery.scene.id} scene={props.gallery.scene} />
|
<TagLink key={scene.id} scene={scene} />
|
||||||
);
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HoverPopover placement="bottom" content={popoverContent}>
|
<HoverPopover placement="bottom" content={popoverContent}>
|
||||||
<Link to={`/scenes/${props.gallery.scene.id}`}>
|
|
||||||
<Button className="minimal">
|
<Button className="minimal">
|
||||||
<Icon icon="play-circle" />
|
<Icon icon="play-circle" />
|
||||||
|
<span>{props.gallery.scenes.length}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
|
||||||
</HoverPopover>
|
</HoverPopover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -124,7 +123,7 @@ export const GalleryCard: React.FC<IProps> = (props) => {
|
|||||||
|
|
||||||
function maybeRenderPopoverButtonGroup() {
|
function maybeRenderPopoverButtonGroup() {
|
||||||
if (
|
if (
|
||||||
props.gallery.scene ||
|
props.gallery.scenes.length > 0 ||
|
||||||
props.gallery.performers.length > 0 ||
|
props.gallery.performers.length > 0 ||
|
||||||
props.gallery.tags.length > 0 ||
|
props.gallery.tags.length > 0 ||
|
||||||
props.gallery.organized
|
props.gallery.organized
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { DeleteGalleriesDialog } from "../DeleteGalleriesDialog";
|
|||||||
import { GalleryImagesPanel } from "./GalleryImagesPanel";
|
import { GalleryImagesPanel } from "./GalleryImagesPanel";
|
||||||
import { GalleryAddPanel } from "./GalleryAddPanel";
|
import { GalleryAddPanel } from "./GalleryAddPanel";
|
||||||
import { GalleryFileInfoPanel } from "./GalleryFileInfoPanel";
|
import { GalleryFileInfoPanel } from "./GalleryFileInfoPanel";
|
||||||
|
import { GalleryScenesPanel } from "./GalleryScenesPanel";
|
||||||
|
|
||||||
interface IGalleryParams {
|
interface IGalleryParams {
|
||||||
id?: string;
|
id?: string;
|
||||||
@@ -118,6 +119,11 @@ export const Gallery: React.FC = () => {
|
|||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Nav.Link eventKey="gallery-details-panel">Details</Nav.Link>
|
<Nav.Link eventKey="gallery-details-panel">Details</Nav.Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
|
{gallery.scenes.length > 0 && (
|
||||||
|
<Nav.Item>
|
||||||
|
<Nav.Link eventKey="gallery-scenes-panel">Scenes</Nav.Link>
|
||||||
|
</Nav.Item>
|
||||||
|
)}
|
||||||
{gallery.path ? (
|
{gallery.path ? (
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Nav.Link eventKey="gallery-file-info-panel">
|
<Nav.Link eventKey="gallery-file-info-panel">
|
||||||
@@ -157,6 +163,11 @@ export const Gallery: React.FC = () => {
|
|||||||
onDelete={() => setIsDeleteAlertOpen(true)}
|
onDelete={() => setIsDeleteAlertOpen(true)}
|
||||||
/>
|
/>
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
|
{gallery.scenes.length > 0 && (
|
||||||
|
<Tab.Pane eventKey="gallery-scenes-panel">
|
||||||
|
<GalleryScenesPanel scenes={gallery.scenes} />
|
||||||
|
</Tab.Pane>
|
||||||
|
)}
|
||||||
</Tab.Content>
|
</Tab.Content>
|
||||||
</Tab.Container>
|
</Tab.Container>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,12 +12,13 @@ import {
|
|||||||
import {
|
import {
|
||||||
PerformerSelect,
|
PerformerSelect,
|
||||||
TagSelect,
|
TagSelect,
|
||||||
|
SceneSelect,
|
||||||
StudioSelect,
|
StudioSelect,
|
||||||
Icon,
|
Icon,
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import { FormUtils, EditableTextUtils } from "src/utils";
|
import { FormUtils, EditableTextUtils, TextUtils } from "src/utils";
|
||||||
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
|
import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars";
|
||||||
import { GalleryScrapeDialog } from "./GalleryScrapeDialog";
|
import { GalleryScrapeDialog } from "./GalleryScrapeDialog";
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ export const GalleryEditPanel: React.FC<
|
|||||||
const [studioId, setStudioId] = useState<string>();
|
const [studioId, setStudioId] = useState<string>();
|
||||||
const [performerIds, setPerformerIds] = useState<string[]>();
|
const [performerIds, setPerformerIds] = useState<string[]>();
|
||||||
const [tagIds, setTagIds] = useState<string[]>();
|
const [tagIds, setTagIds] = useState<string[]>();
|
||||||
|
const [scenes, setScenes] = useState<{ id: string; title: string }[]>([]);
|
||||||
|
|
||||||
const Scrapers = useListGalleryScrapers();
|
const Scrapers = useListGalleryScrapers();
|
||||||
|
|
||||||
@@ -117,6 +119,12 @@ export const GalleryEditPanel: React.FC<
|
|||||||
setStudioId(state?.studio?.id ?? undefined);
|
setStudioId(state?.studio?.id ?? undefined);
|
||||||
setPerformerIds(perfIds);
|
setPerformerIds(perfIds);
|
||||||
setTagIds(tIds);
|
setTagIds(tIds);
|
||||||
|
setScenes(
|
||||||
|
(state?.scenes ?? []).map((s) => ({
|
||||||
|
id: s.id,
|
||||||
|
title: s.title ?? TextUtils.fileNameFromPath(s.path ?? ""),
|
||||||
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -135,6 +143,7 @@ export const GalleryEditPanel: React.FC<
|
|||||||
studio_id: studioId ?? null,
|
studio_id: studioId ?? null,
|
||||||
performer_ids: performerIds,
|
performer_ids: performerIds,
|
||||||
tag_ids: tagIds,
|
tag_ids: tagIds,
|
||||||
|
scene_ids: scenes.map((s) => s.id),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,6 +399,23 @@ export const GalleryEditPanel: React.FC<
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group controlId="scenes" as={Row}>
|
||||||
|
{FormUtils.renderLabel({
|
||||||
|
title: "Scenes",
|
||||||
|
labelProps: {
|
||||||
|
column: true,
|
||||||
|
sm: 3,
|
||||||
|
xl: 12,
|
||||||
|
},
|
||||||
|
})}
|
||||||
|
<Col sm={9} xl={12}>
|
||||||
|
<SceneSelect
|
||||||
|
scenes={scenes}
|
||||||
|
onSelect={(items) => setScenes(items)}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Form.Group>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-lg-6 col-xl-12">
|
<div className="col-12 col-lg-6 col-xl-12">
|
||||||
<Form.Group controlId="details">
|
<Form.Group controlId="details">
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from "react";
|
||||||
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
import { SceneCard } from "src/components/Scenes/SceneCard";
|
||||||
|
|
||||||
|
interface IGalleryScenesPanelProps {
|
||||||
|
scenes: GQL.SceneDataFragment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GalleryScenesPanel: React.FC<IGalleryScenesPanelProps> = ({
|
||||||
|
scenes,
|
||||||
|
}) => (
|
||||||
|
<div className="container gallery-scenes">
|
||||||
|
{scenes.map((scene) => (
|
||||||
|
<SceneCard scene={scene} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import { useFindGallery } from "src/core/StashService";
|
||||||
import { useLightbox } from "src/hooks";
|
import { useLightbox } from "src/hooks";
|
||||||
|
import { LoadingIndicator } from "src/components/Shared";
|
||||||
import "flexbin/flexbin.css";
|
import "flexbin/flexbin.css";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
gallery: GQL.GalleryDataFragment;
|
galleryId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GalleryViewer: React.FC<IProps> = ({ gallery }) => {
|
export const GalleryViewer: React.FC<IProps> = ({ galleryId }) => {
|
||||||
const images = gallery?.images ?? [];
|
const { data, loading } = useFindGallery(galleryId);
|
||||||
|
const images = data?.findGallery?.images ?? [];
|
||||||
const showLightbox = useLightbox({ images, showNavigation: false });
|
const showLightbox = useLightbox({ images, showNavigation: false });
|
||||||
|
|
||||||
|
if (loading) return <LoadingIndicator />;
|
||||||
|
|
||||||
const thumbs = images.map((file, index) => (
|
const thumbs = images.map((file, index) => (
|
||||||
<div
|
<div
|
||||||
role="link"
|
role="link"
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ $galleryTabWidth: 450px;
|
|||||||
&-gallery {
|
&-gallery {
|
||||||
height: 22.5rem;
|
height: 22.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-image {
|
||||||
|
height: 22.5rem;
|
||||||
|
width: 15rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ export const ScenePreview: React.FC<IScenePreviewProps> = ({
|
|||||||
interface ISceneCardProps {
|
interface ISceneCardProps {
|
||||||
scene: GQL.SlimSceneDataFragment;
|
scene: GQL.SlimSceneDataFragment;
|
||||||
selecting?: boolean;
|
selecting?: boolean;
|
||||||
selected: boolean | undefined;
|
selected?: boolean | undefined;
|
||||||
zoomIndex: number;
|
zoomIndex?: number;
|
||||||
onSelectedChanged: (selected: boolean, shiftKey: boolean) => void;
|
onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SceneCard: React.FC<ISceneCardProps> = (
|
export const SceneCard: React.FC<ISceneCardProps> = (
|
||||||
@@ -257,18 +257,21 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
function maybeRenderGallery() {
|
function maybeRenderGallery() {
|
||||||
if (props.scene.gallery) {
|
if (props.scene.galleries.length <= 0) return;
|
||||||
|
|
||||||
|
const popoverContent = props.scene.galleries.map((gallery) => (
|
||||||
|
<TagLink key={gallery.id} gallery={gallery} />
|
||||||
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<HoverPopover placement="bottom" content={popoverContent}>
|
||||||
<Link to={`/galleries/${props.scene.gallery.id}`}>
|
|
||||||
<Button className="minimal">
|
<Button className="minimal">
|
||||||
<Icon icon="image" />
|
<Icon icon="image" />
|
||||||
|
<span>{props.scene.galleries.length}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</HoverPopover>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function maybeRenderOrganized() {
|
function maybeRenderOrganized() {
|
||||||
if (props.scene.organized) {
|
if (props.scene.organized) {
|
||||||
@@ -289,7 +292,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
props.scene.movies.length > 0 ||
|
props.scene.movies.length > 0 ||
|
||||||
props.scene.scene_markers.length > 0 ||
|
props.scene.scene_markers.length > 0 ||
|
||||||
props.scene?.o_counter ||
|
props.scene?.o_counter ||
|
||||||
props.scene.gallery ||
|
props.scene.galleries.length > 0 ||
|
||||||
props.scene.organized
|
props.scene.organized
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
@@ -314,7 +317,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
) {
|
) {
|
||||||
const { shiftKey } = event;
|
const { shiftKey } = event;
|
||||||
|
|
||||||
if (props.selecting) {
|
if (props.selecting && props.onSelectedChanged) {
|
||||||
props.onSelectedChanged(!props.selected, shiftKey);
|
props.onSelectedChanged(!props.selected, shiftKey);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
@@ -331,7 +334,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
const ev = event;
|
const ev = event;
|
||||||
const shiftKey = false;
|
const shiftKey = false;
|
||||||
|
|
||||||
if (props.selecting && !props.selected) {
|
if (props.selecting && props.onSelectedChanged && !props.selected) {
|
||||||
props.onSelectedChanged(true, shiftKey);
|
props.onSelectedChanged(true, shiftKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +357,7 @@ export const SceneCard: React.FC<ISceneCardProps> = (
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="scene-card-check"
|
className="scene-card-check"
|
||||||
checked={props.selected}
|
checked={props.selected}
|
||||||
onChange={() => props.onSelectedChanged(!props.selected, shiftKey)}
|
onChange={() => props.onSelectedChanged?.(!props.selected, shiftKey)}
|
||||||
onClick={(event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
|
onClick={(event: React.MouseEvent<HTMLInputElement, MouseEvent>) => {
|
||||||
// eslint-disable-next-line prefer-destructuring
|
// eslint-disable-next-line prefer-destructuring
|
||||||
shiftKey = event.shiftKey;
|
shiftKey = event.shiftKey;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { SceneEditPanel } from "./SceneEditPanel";
|
|||||||
import { SceneDetailPanel } from "./SceneDetailPanel";
|
import { SceneDetailPanel } from "./SceneDetailPanel";
|
||||||
import { OCounterButton } from "./OCounterButton";
|
import { OCounterButton } from "./OCounterButton";
|
||||||
import { SceneMoviePanel } from "./SceneMoviePanel";
|
import { SceneMoviePanel } from "./SceneMoviePanel";
|
||||||
|
import { SceneGalleriesPanel } from "./SceneGalleriesPanel";
|
||||||
import { DeleteScenesDialog } from "../DeleteScenesDialog";
|
import { DeleteScenesDialog } from "../DeleteScenesDialog";
|
||||||
import { SceneGenerateDialog } from "../SceneGenerateDialog";
|
import { SceneGenerateDialog } from "../SceneGenerateDialog";
|
||||||
import { SceneVideoFilterPanel } from "./SceneVideoFilterPanel";
|
import { SceneVideoFilterPanel } from "./SceneVideoFilterPanel";
|
||||||
@@ -243,13 +244,16 @@ export const Scene: React.FC = () => {
|
|||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
)}
|
)}
|
||||||
{scene.gallery ? (
|
{scene.galleries.length === 1 ? (
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Nav.Link eventKey="scene-gallery-panel">Gallery</Nav.Link>
|
<Nav.Link eventKey="scene-gallery-panel">Gallery</Nav.Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
) : (
|
) : undefined}
|
||||||
""
|
{scene.galleries.length > 1 ? (
|
||||||
)}
|
<Nav.Item>
|
||||||
|
<Nav.Link eventKey="scene-galleries-panel">Galleries</Nav.Link>
|
||||||
|
</Nav.Item>
|
||||||
|
) : undefined}
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Nav.Link eventKey="scene-video-filter-panel">Filters</Nav.Link>
|
<Nav.Link eventKey="scene-video-filter-panel">Filters</Nav.Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
@@ -295,12 +299,15 @@ export const Scene: React.FC = () => {
|
|||||||
<Tab.Pane eventKey="scene-movie-panel">
|
<Tab.Pane eventKey="scene-movie-panel">
|
||||||
<SceneMoviePanel scene={scene} />
|
<SceneMoviePanel scene={scene} />
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
{scene.gallery ? (
|
{scene.galleries.length === 1 && (
|
||||||
<Tab.Pane eventKey="scene-gallery-panel">
|
<Tab.Pane eventKey="scene-gallery-panel">
|
||||||
<GalleryViewer gallery={scene.gallery} />
|
<GalleryViewer galleryId={scene.galleries[0].id} />
|
||||||
|
</Tab.Pane>
|
||||||
|
)}
|
||||||
|
{scene.galleries.length > 1 && (
|
||||||
|
<Tab.Pane eventKey="scene-galleries-panel">
|
||||||
|
<SceneGalleriesPanel galleries={scene.galleries} />
|
||||||
</Tab.Pane>
|
</Tab.Pane>
|
||||||
) : (
|
|
||||||
""
|
|
||||||
)}
|
)}
|
||||||
<Tab.Pane eventKey="scene-video-filter-panel">
|
<Tab.Pane eventKey="scene-video-filter-panel">
|
||||||
<SceneVideoFilterPanel scene={scene} />
|
<SceneVideoFilterPanel scene={scene} />
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ import {
|
|||||||
PerformerSelect,
|
PerformerSelect,
|
||||||
TagSelect,
|
TagSelect,
|
||||||
StudioSelect,
|
StudioSelect,
|
||||||
SceneGallerySelect,
|
GallerySelect,
|
||||||
Icon,
|
Icon,
|
||||||
LoadingIndicator,
|
LoadingIndicator,
|
||||||
ImageInput,
|
ImageInput,
|
||||||
} from "src/components/Shared";
|
} from "src/components/Shared";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import { ImageUtils, FormUtils, EditableTextUtils } from "src/utils";
|
import { ImageUtils, FormUtils, EditableTextUtils, TextUtils } from "src/utils";
|
||||||
import { MovieSelect } from "src/components/Shared/Select";
|
import { MovieSelect } from "src/components/Shared/Select";
|
||||||
import { SceneMovieTable, MovieSceneIndexMap } from "./SceneMovieTable";
|
import { SceneMovieTable, MovieSceneIndexMap } from "./SceneMovieTable";
|
||||||
import { RatingStars } from "./RatingStars";
|
import { RatingStars } from "./RatingStars";
|
||||||
@@ -47,7 +47,9 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||||||
const [url, setUrl] = useState<string>();
|
const [url, setUrl] = useState<string>();
|
||||||
const [date, setDate] = useState<string>();
|
const [date, setDate] = useState<string>();
|
||||||
const [rating, setRating] = useState<number>();
|
const [rating, setRating] = useState<number>();
|
||||||
const [galleryId, setGalleryId] = useState<string>();
|
const [galleries, setGalleries] = useState<{ id: string; title: string }[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
const [studioId, setStudioId] = useState<string>();
|
const [studioId, setStudioId] = useState<string>();
|
||||||
const [performerIds, setPerformerIds] = useState<string[]>();
|
const [performerIds, setPerformerIds] = useState<string[]>();
|
||||||
const [movieIds, setMovieIds] = useState<string[] | undefined>(undefined);
|
const [movieIds, setMovieIds] = useState<string[] | undefined>(undefined);
|
||||||
@@ -171,7 +173,12 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||||||
setUrl(state.url ?? undefined);
|
setUrl(state.url ?? undefined);
|
||||||
setDate(state.date ?? undefined);
|
setDate(state.date ?? undefined);
|
||||||
setRating(state.rating === null ? NaN : state.rating);
|
setRating(state.rating === null ? NaN : state.rating);
|
||||||
setGalleryId(state?.gallery?.id ?? undefined);
|
setGalleries(
|
||||||
|
(state?.galleries ?? []).map((g) => ({
|
||||||
|
id: g.id,
|
||||||
|
title: g.title ?? TextUtils.fileNameFromPath(g.path ?? ""),
|
||||||
|
}))
|
||||||
|
);
|
||||||
setStudioId(state?.studio?.id ?? undefined);
|
setStudioId(state?.studio?.id ?? undefined);
|
||||||
setMovieIds(moviIds);
|
setMovieIds(moviIds);
|
||||||
setMovieSceneIndexes(movieSceneIdx);
|
setMovieSceneIndexes(movieSceneIdx);
|
||||||
@@ -196,7 +203,7 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||||||
url,
|
url,
|
||||||
date,
|
date,
|
||||||
rating: rating ?? null,
|
rating: rating ?? null,
|
||||||
gallery_id: galleryId ?? null,
|
gallery_ids: galleries.map((g) => g.id),
|
||||||
studio_id: studioId ?? null,
|
studio_id: studioId ?? null,
|
||||||
performer_ids: performerIds,
|
performer_ids: performerIds,
|
||||||
movies: makeMovieInputs(),
|
movies: makeMovieInputs(),
|
||||||
@@ -596,15 +603,14 @@ export const SceneEditPanel: React.FC<IProps> = (props: IProps) => {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group controlId="gallery" as={Row}>
|
<Form.Group controlId="galleries" as={Row}>
|
||||||
{FormUtils.renderLabel({
|
{FormUtils.renderLabel({
|
||||||
title: "Gallery",
|
title: "Galleries",
|
||||||
})}
|
})}
|
||||||
<Col xs={9}>
|
<Col xs={9}>
|
||||||
<SceneGallerySelect
|
<GallerySelect
|
||||||
sceneId={props.scene.id}
|
galleries={galleries}
|
||||||
gallery={props.scene.gallery ?? undefined}
|
onSelect={(items) => setGalleries(items)}
|
||||||
onSelect={(item) => setGalleryId(item ? item.id : undefined)}
|
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from "react";
|
||||||
|
import * as GQL from "src/core/generated-graphql";
|
||||||
|
import { GalleryCard } from "src/components/Galleries/GalleryCard";
|
||||||
|
|
||||||
|
interface ISceneGalleriesPanelProps {
|
||||||
|
galleries: GQL.GallerySlimDataFragment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SceneGalleriesPanel: React.FC<ISceneGalleriesPanelProps> = ({
|
||||||
|
galleries,
|
||||||
|
}) => {
|
||||||
|
const cards = galleries.map((gallery) => (
|
||||||
|
<GalleryCard key={gallery.id} gallery={gallery} selecting={false} />
|
||||||
|
));
|
||||||
|
|
||||||
|
return <div className="row justify-content-center">{cards}</div>;
|
||||||
|
};
|
||||||
@@ -14,11 +14,9 @@ import {
|
|||||||
useTagCreate,
|
useTagCreate,
|
||||||
useStudioCreate,
|
useStudioCreate,
|
||||||
usePerformerCreate,
|
usePerformerCreate,
|
||||||
useFindGalleries,
|
|
||||||
} from "src/core/StashService";
|
} from "src/core/StashService";
|
||||||
import { useToast } from "src/hooks";
|
import { useToast } from "src/hooks";
|
||||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
import { TextUtils } from "src/utils";
|
||||||
import { FilterMode } from "src/models/list-filter/types";
|
|
||||||
|
|
||||||
export type ValidTypes =
|
export type ValidTypes =
|
||||||
| GQL.SlimPerformerDataFragment
|
| GQL.SlimPerformerDataFragment
|
||||||
@@ -72,23 +70,28 @@ interface IFilterComponentProps extends IFilterProps {
|
|||||||
interface IFilterSelectProps<T extends boolean>
|
interface IFilterSelectProps<T extends boolean>
|
||||||
extends Omit<ISelectProps<T>, "onChange" | "items" | "onCreateOption"> {}
|
extends Omit<ISelectProps<T>, "onChange" | "items" | "onCreateOption"> {}
|
||||||
|
|
||||||
interface ISceneGallerySelect {
|
type Gallery = { id: string; title: string };
|
||||||
gallery?: Pick<GQL.Gallery, "title" | "path" | "id">;
|
interface IGallerySelect {
|
||||||
sceneId: string;
|
galleries: Gallery[];
|
||||||
onSelect: (
|
onSelect: (items: Gallery[]) => void;
|
||||||
item:
|
|
||||||
| GQL.ValidGalleriesForSceneQuery["validGalleriesForScene"][0]
|
|
||||||
| undefined
|
|
||||||
) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSelectedValues = (selectedItems: ValueType<Option, boolean>) =>
|
type Scene = { id: string; title: string };
|
||||||
|
interface ISceneSelect {
|
||||||
|
scenes: Scene[];
|
||||||
|
onSelect: (items: Scene[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSelectedItems = (selectedItems: ValueType<Option, boolean>) =>
|
||||||
selectedItems
|
selectedItems
|
||||||
? (Array.isArray(selectedItems) ? selectedItems : [selectedItems]).map(
|
? Array.isArray(selectedItems)
|
||||||
(item) => item.value
|
? selectedItems
|
||||||
)
|
: [selectedItems]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const getSelectedValues = (selectedItems: ValueType<Option, boolean>) =>
|
||||||
|
getSelectedItems(selectedItems).map((item) => item.value);
|
||||||
|
|
||||||
const SelectComponent = <T extends boolean>({
|
const SelectComponent = <T extends boolean>({
|
||||||
type,
|
type,
|
||||||
initialIds,
|
initialIds,
|
||||||
@@ -231,53 +234,104 @@ const FilterSelectComponent = <T extends boolean>(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SceneGallerySelect: React.FC<ISceneGallerySelect> = (props) => {
|
export const GallerySelect: React.FC<IGallerySelect> = (props) => {
|
||||||
const [query, setQuery] = useState<string>("");
|
const [query, setQuery] = useState<string>("");
|
||||||
const { data, loading } = useFindGalleries(getFilter());
|
const { data, loading } = GQL.useFindGalleriesQuery({
|
||||||
const [selectedOption, setSelectedOption] = useState<Option>();
|
skip: query === "",
|
||||||
|
variables: {
|
||||||
|
filter: {
|
||||||
|
q: query,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const galleries = data?.findGalleries.galleries ?? [];
|
const galleries = data?.findGalleries.galleries ?? [];
|
||||||
const items = galleries.map((g) => ({
|
const items = galleries.map((g) => ({
|
||||||
label: g.title ?? g.path ?? "",
|
label: g.title ?? TextUtils.fileNameFromPath(g.path ?? ""),
|
||||||
value: g.id,
|
value: g.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function getFilter() {
|
|
||||||
const ret = new ListFilterModel(FilterMode.Galleries);
|
|
||||||
ret.searchTerm = query;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onInputChange = debounce((input: string) => {
|
const onInputChange = debounce((input: string) => {
|
||||||
setQuery(input);
|
setQuery(input);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
const onChange = (selectedItem: ValueType<Option, false>) => {
|
const onChange = (selectedItems: ValueType<Option, boolean>) => {
|
||||||
setSelectedOption(selectedItem ?? undefined);
|
const selected = getSelectedItems(selectedItems);
|
||||||
props.onSelect(
|
props.onSelect(
|
||||||
selectedItem
|
selected.map((s) => ({
|
||||||
? galleries.find((g) => g.id === selectedItem.value)
|
id: s.value,
|
||||||
: undefined
|
title: s.label,
|
||||||
|
}))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const option =
|
const options = props.galleries.map((g) => ({
|
||||||
selectedOption ??
|
value: g.id,
|
||||||
(props.gallery
|
label: g.title ?? "Unknown",
|
||||||
? {
|
}));
|
||||||
value: props.gallery.id,
|
|
||||||
label: props.gallery.title ?? props.gallery.path ?? "Unknown",
|
|
||||||
}
|
|
||||||
: undefined);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectComponent
|
<SelectComponent
|
||||||
isMulti={false}
|
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onInputChange={onInputChange}
|
onInputChange={onInputChange}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
items={items}
|
items={items}
|
||||||
selectedOptions={option}
|
selectedOptions={options}
|
||||||
|
isMulti
|
||||||
|
placeholder="Search for gallery..."
|
||||||
|
noOptionsMessage={query === "" ? null : "No galleries found."}
|
||||||
|
showDropdown={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SceneSelect: React.FC<ISceneSelect> = (props) => {
|
||||||
|
const [query, setQuery] = useState<string>("");
|
||||||
|
const { data, loading } = GQL.useFindScenesQuery({
|
||||||
|
skip: query === "",
|
||||||
|
variables: {
|
||||||
|
filter: {
|
||||||
|
q: query,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const scenes = data?.findScenes.scenes ?? [];
|
||||||
|
const items = scenes.map((s) => ({
|
||||||
|
label: s.title ?? TextUtils.fileNameFromPath(s.path ?? ""),
|
||||||
|
value: s.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const onInputChange = debounce((input: string) => {
|
||||||
|
setQuery(input);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
const onChange = (selectedItems: ValueType<Option, true>) => {
|
||||||
|
const selected = getSelectedItems(selectedItems);
|
||||||
|
props.onSelect(
|
||||||
|
(selected ?? []).map((s) => ({
|
||||||
|
id: s.value,
|
||||||
|
title: s.label,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = props.scenes.map((s) => ({
|
||||||
|
value: s.id,
|
||||||
|
label: s.title,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectComponent
|
||||||
|
onChange={onChange}
|
||||||
|
onInputChange={onInputChange}
|
||||||
|
isLoading={loading}
|
||||||
|
items={items}
|
||||||
|
selectedOptions={options}
|
||||||
|
isMulti
|
||||||
|
placeholder="Search for scene..."
|
||||||
|
noOptionsMessage={query === "" ? null : "No scenes found."}
|
||||||
|
showDropdown={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
export {
|
export {
|
||||||
SceneGallerySelect,
|
GallerySelect,
|
||||||
ScrapePerformerSuggest,
|
ScrapePerformerSuggest,
|
||||||
MarkerTitleSuggest,
|
MarkerTitleSuggest,
|
||||||
FilterSelect,
|
FilterSelect,
|
||||||
PerformerSelect,
|
PerformerSelect,
|
||||||
StudioSelect,
|
StudioSelect,
|
||||||
TagSelect,
|
TagSelect,
|
||||||
|
SceneSelect,
|
||||||
} from "./Select";
|
} from "./Select";
|
||||||
|
|
||||||
export { default as Icon } from "./Icon";
|
export { default as Icon } from "./Icon";
|
||||||
|
|||||||
@@ -33,13 +33,13 @@
|
|||||||
padding: 1rem 0;
|
padding: 1rem 0;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: hsl(204, 20, 30);
|
background-color: hsl(204, 20%, 30%);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-result {
|
.selected-result {
|
||||||
background-color: hsl(204, 20, 30);
|
background-color: hsl(204, 20%, 30%);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -264,10 +264,6 @@ export const useAllPerformersForFilter = () =>
|
|||||||
GQL.useAllPerformersForFilterQuery();
|
GQL.useAllPerformersForFilterQuery();
|
||||||
export const useAllStudiosForFilter = () => GQL.useAllStudiosForFilterQuery();
|
export const useAllStudiosForFilter = () => GQL.useAllStudiosForFilterQuery();
|
||||||
export const useAllMoviesForFilter = () => GQL.useAllMoviesForFilterQuery();
|
export const useAllMoviesForFilter = () => GQL.useAllMoviesForFilterQuery();
|
||||||
export const useValidGalleriesForScene = (sceneId: string) =>
|
|
||||||
GQL.useValidGalleriesForSceneQuery({
|
|
||||||
variables: { scene_id: sceneId },
|
|
||||||
});
|
|
||||||
export const useStats = () => GQL.useStatsQuery();
|
export const useStats = () => GQL.useStatsQuery();
|
||||||
export const useVersion = () => GQL.useVersionQuery();
|
export const useVersion = () => GQL.useVersionQuery();
|
||||||
export const useLatestVersion = () =>
|
export const useLatestVersion = () =>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class SceneIsMissingCriterion extends IsMissingCriterion {
|
|||||||
"details",
|
"details",
|
||||||
"url",
|
"url",
|
||||||
"date",
|
"date",
|
||||||
"gallery",
|
"galleries",
|
||||||
"studio",
|
"studio",
|
||||||
"movie",
|
"movie",
|
||||||
"performers",
|
"performers",
|
||||||
@@ -83,7 +83,7 @@ export class GalleryIsMissingCriterion extends IsMissingCriterion {
|
|||||||
"studio",
|
"studio",
|
||||||
"performers",
|
"performers",
|
||||||
"tags",
|
"tags",
|
||||||
"scene",
|
"scenes",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user