Separate graphql API from rest of the system (#2503)

* Move graphql generated files to api
* Refactor identify options
* Remove models.StashBoxes
* Move ScraperSource to scraper package
* Rename field strategy enums
* Rename identify.TaskOptions to Options
This commit is contained in:
WithoutPants 2022-04-25 15:55:05 +10:00
parent 9dcf03eb70
commit 7b5bd80515
109 changed files with 2684 additions and 791 deletions

2
.gitignore vendored
View file

@ -16,7 +16,7 @@
*.out *.out
# GraphQL generated output # GraphQL generated output
pkg/models/generated_*.go internal/api/generated_*.go
ui/v2.5/src/core/generated-*.tsx ui/v2.5/src/core/generated-*.tsx
#### ####

View file

@ -4,46 +4,110 @@ schema:
- "graphql/schema/types/*.graphql" - "graphql/schema/types/*.graphql"
- "graphql/schema/*.graphql" - "graphql/schema/*.graphql"
exec: exec:
filename: pkg/models/generated_exec.go filename: internal/api/generated_exec.go
model: model:
filename: pkg/models/generated_models.go filename: internal/api/generated_models.go
resolver: resolver:
filename: internal/api/resolver.go filename: internal/api/resolver.go
type: Resolver type: Resolver
struct_tag: gqlgen struct_tag: gqlgen
autobind:
- github.com/stashapp/stash/pkg/models
- github.com/stashapp/stash/pkg/plugin
- github.com/stashapp/stash/pkg/scraper
- github.com/stashapp/stash/internal/identify
- github.com/stashapp/stash/internal/dlna
- github.com/stashapp/stash/pkg/scraper/stashbox
models: models:
# autobind on config causes generation issues
# Scalars # Scalars
Timestamp: Timestamp:
model: github.com/stashapp/stash/pkg/models.Timestamp model: github.com/stashapp/stash/pkg/models.Timestamp
# Objects StashConfig:
Gallery: model: github.com/stashapp/stash/internal/manager/config.StashConfig
model: github.com/stashapp/stash/pkg/models.Gallery StashConfigInput:
Image: model: github.com/stashapp/stash/internal/manager/config.StashConfigInput
model: github.com/stashapp/stash/pkg/models.Image StashBoxInput:
ImageFileType: model: github.com/stashapp/stash/internal/manager/config.StashBoxInput
model: github.com/stashapp/stash/pkg/models.ImageFileType ConfigImageLightboxResult:
Performer: model: github.com/stashapp/stash/internal/manager/config.ConfigImageLightboxResult
model: github.com/stashapp/stash/pkg/models.Performer ImageLightboxDisplayMode:
Scene: model: github.com/stashapp/stash/internal/manager/config.ImageLightboxDisplayMode
model: github.com/stashapp/stash/pkg/models.Scene ImageLightboxScrollMode:
SceneMarker: model: github.com/stashapp/stash/internal/manager/config.ImageLightboxScrollMode
model: github.com/stashapp/stash/pkg/models.SceneMarker ConfigDisableDropdownCreate:
ScrapedItem: model: github.com/stashapp/stash/internal/manager/config.ConfigDisableDropdownCreate
model: github.com/stashapp/stash/pkg/models.ScrapedItem ScanMetadataOptions:
Studio: model: github.com/stashapp/stash/internal/manager/config.ScanMetadataOptions
model: github.com/stashapp/stash/pkg/models.Studio AutoTagMetadataOptions:
Movie: model: github.com/stashapp/stash/internal/manager/config.AutoTagMetadataOptions
model: github.com/stashapp/stash/pkg/models.Movie SceneParserInput:
Tag: model: github.com/stashapp/stash/internal/manager.SceneParserInput
model: github.com/stashapp/stash/pkg/models.Tag SceneParserResult:
SceneFileType: model: github.com/stashapp/stash/internal/manager.SceneParserResult
model: github.com/stashapp/stash/pkg/models.SceneFileType SceneMovieID:
SavedFilter: model: github.com/stashapp/stash/internal/manager.SceneMovieID
model: github.com/stashapp/stash/pkg/models.SavedFilter SystemStatus:
StashID: model: github.com/stashapp/stash/internal/manager.SystemStatus
model: github.com/stashapp/stash/pkg/models.StashID SystemStatusEnum:
SceneCaption: model: github.com/stashapp/stash/internal/manager.SystemStatusEnum
model: github.com/stashapp/stash/pkg/models.SceneCaption ImportDuplicateEnum:
model: github.com/stashapp/stash/internal/manager.ImportDuplicateEnum
SetupInput:
model: github.com/stashapp/stash/internal/manager.SetupInput
MigrateInput:
model: github.com/stashapp/stash/internal/manager.MigrateInput
ScanMetadataInput:
model: github.com/stashapp/stash/internal/manager.ScanMetadataInput
GenerateMetadataInput:
model: github.com/stashapp/stash/internal/manager.GenerateMetadataInput
GeneratePreviewOptionsInput:
model: github.com/stashapp/stash/internal/manager.GeneratePreviewOptionsInput
AutoTagMetadataInput:
model: github.com/stashapp/stash/internal/manager.AutoTagMetadataInput
CleanMetadataInput:
model: github.com/stashapp/stash/internal/manager.CleanMetadataInput
StashBoxBatchPerformerTagInput:
model: github.com/stashapp/stash/internal/manager.StashBoxBatchPerformerTagInput
SceneStreamEndpoint:
model: github.com/stashapp/stash/internal/manager.SceneStreamEndpoint
ExportObjectTypeInput:
model: github.com/stashapp/stash/internal/manager.ExportObjectTypeInput
ExportObjectsInput:
model: github.com/stashapp/stash/internal/manager.ExportObjectsInput
ImportObjectsInput:
model: github.com/stashapp/stash/internal/manager.ImportObjectsInput
ScanMetaDataFilterInput:
model: github.com/stashapp/stash/internal/manager.ScanMetaDataFilterInput
# renamed types
DLNAStatus:
model: github.com/stashapp/stash/internal/dlna.Status
DLNAIP:
model: github.com/stashapp/stash/internal/dlna.Dlnaip
IdentifySource:
model: github.com/stashapp/stash/internal/identify.Source
IdentifyMetadataTaskOptions:
model: github.com/stashapp/stash/internal/identify.Options
IdentifyMetadataInput:
model: github.com/stashapp/stash/internal/identify.Options
IdentifyMetadataOptions:
model: github.com/stashapp/stash/internal/identify.MetadataOptions
IdentifyFieldOptions:
model: github.com/stashapp/stash/internal/identify.FieldOptions
IdentifyFieldStrategy:
model: github.com/stashapp/stash/internal/identify.FieldStrategy
ScraperSource:
model: github.com/stashapp/stash/pkg/scraper.Source
# rebind inputs to types
IdentifySourceInput:
model: github.com/stashapp/stash/internal/identify.Source
IdentifyFieldOptionsInput:
model: github.com/stashapp/stash/internal/identify.FieldOptions
IdentifyMetadataOptionsInput:
model: github.com/stashapp/stash/internal/identify.MetadataOptions
ScraperSourceInput:
model: github.com/stashapp/stash/pkg/scraper.Source

View file

@ -38,37 +38,37 @@ func (r *Resolver) scraperCache() *scraper.Cache {
return manager.GetInstance().ScraperCache return manager.GetInstance().ScraperCache
} }
func (r *Resolver) Gallery() models.GalleryResolver { func (r *Resolver) Gallery() GalleryResolver {
return &galleryResolver{r} return &galleryResolver{r}
} }
func (r *Resolver) Mutation() models.MutationResolver { func (r *Resolver) Mutation() MutationResolver {
return &mutationResolver{r} return &mutationResolver{r}
} }
func (r *Resolver) Performer() models.PerformerResolver { func (r *Resolver) Performer() PerformerResolver {
return &performerResolver{r} return &performerResolver{r}
} }
func (r *Resolver) Query() models.QueryResolver { func (r *Resolver) Query() QueryResolver {
return &queryResolver{r} return &queryResolver{r}
} }
func (r *Resolver) Scene() models.SceneResolver { func (r *Resolver) Scene() SceneResolver {
return &sceneResolver{r} return &sceneResolver{r}
} }
func (r *Resolver) Image() models.ImageResolver { func (r *Resolver) Image() ImageResolver {
return &imageResolver{r} return &imageResolver{r}
} }
func (r *Resolver) SceneMarker() models.SceneMarkerResolver { func (r *Resolver) SceneMarker() SceneMarkerResolver {
return &sceneMarkerResolver{r} return &sceneMarkerResolver{r}
} }
func (r *Resolver) Studio() models.StudioResolver { func (r *Resolver) Studio() StudioResolver {
return &studioResolver{r} return &studioResolver{r}
} }
func (r *Resolver) Movie() models.MovieResolver { func (r *Resolver) Movie() MovieResolver {
return &movieResolver{r} return &movieResolver{r}
} }
func (r *Resolver) Subscription() models.SubscriptionResolver { func (r *Resolver) Subscription() SubscriptionResolver {
return &subscriptionResolver{r} return &subscriptionResolver{r}
} }
func (r *Resolver) Tag() models.TagResolver { func (r *Resolver) Tag() TagResolver {
return &tagResolver{r} return &tagResolver{r}
} }
@ -125,8 +125,8 @@ func (r *queryResolver) MarkerStrings(ctx context.Context, q *string, sort *stri
return ret, nil return ret, nil
} }
func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, error) { func (r *queryResolver) Stats(ctx context.Context) (*StatsResultType, error) {
var ret models.StatsResultType var ret StatsResultType
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
scenesQB := repo.Scene() scenesQB := repo.Scene()
imageQB := repo.Image() imageQB := repo.Image()
@ -146,7 +146,7 @@ func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, err
moviesCount, _ := moviesQB.Count() moviesCount, _ := moviesQB.Count()
tagsCount, _ := tagsQB.Count() tagsCount, _ := tagsQB.Count()
ret = models.StatsResultType{ ret = StatsResultType{
SceneCount: scenesCount, SceneCount: scenesCount,
ScenesSize: scenesSize, ScenesSize: scenesSize,
ScenesDuration: scenesDuration, ScenesDuration: scenesDuration,
@ -167,10 +167,10 @@ func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, err
return &ret, nil return &ret, nil
} }
func (r *queryResolver) Version(ctx context.Context) (*models.Version, error) { func (r *queryResolver) Version(ctx context.Context) (*Version, error) {
version, hash, buildtime := GetVersion() version, hash, buildtime := GetVersion()
return &models.Version{ return &Version{
Version: &version, Version: &version,
Hash: hash, Hash: hash,
BuildTime: buildtime, BuildTime: buildtime,
@ -178,7 +178,7 @@ func (r *queryResolver) Version(ctx context.Context) (*models.Version, error) {
} }
// Latestversion returns the latest git shorthash commit. // Latestversion returns the latest git shorthash commit.
func (r *queryResolver) Latestversion(ctx context.Context) (*models.ShortVersion, error) { func (r *queryResolver) Latestversion(ctx context.Context) (*ShortVersion, error) {
ver, url, err := GetLatestVersion(ctx, true) ver, url, err := GetLatestVersion(ctx, true)
if err == nil { if err == nil {
logger.Infof("Retrieved latest hash: %s", ver) logger.Infof("Retrieved latest hash: %s", ver)
@ -186,21 +186,21 @@ func (r *queryResolver) Latestversion(ctx context.Context) (*models.ShortVersion
logger.Errorf("Error while retrieving latest hash: %s", err) logger.Errorf("Error while retrieving latest hash: %s", err)
} }
return &models.ShortVersion{ return &ShortVersion{
Shorthash: ver, Shorthash: ver,
URL: url, URL: url,
}, err }, err
} }
// Get scene marker tags which show up under the video. // Get scene marker tags which show up under the video.
func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([]*models.SceneMarkerTag, error) { func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([]*SceneMarkerTag, error) {
sceneID, err := strconv.Atoi(scene_id) sceneID, err := strconv.Atoi(scene_id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var keys []int var keys []int
tags := make(map[int]*models.SceneMarkerTag) tags := make(map[int]*SceneMarkerTag)
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
sceneMarkers, err := repo.SceneMarker().FindBySceneID(sceneID) sceneMarkers, err := repo.SceneMarker().FindBySceneID(sceneID)
@ -216,7 +216,7 @@ func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([
} }
_, hasKey := tags[markerPrimaryTag.ID] _, hasKey := tags[markerPrimaryTag.ID]
if !hasKey { if !hasKey {
sceneMarkerTag := &models.SceneMarkerTag{Tag: markerPrimaryTag} sceneMarkerTag := &SceneMarkerTag{Tag: markerPrimaryTag}
tags[markerPrimaryTag.ID] = sceneMarkerTag tags[markerPrimaryTag.ID] = sceneMarkerTag
keys = append(keys, markerPrimaryTag.ID) keys = append(keys, markerPrimaryTag.ID)
} }
@ -235,7 +235,7 @@ func (r *queryResolver) SceneMarkerTags(ctx context.Context, scene_id string) ([
return a.SceneMarkers[0].Seconds < b.SceneMarkers[0].Seconds return a.SceneMarkers[0].Seconds < b.SceneMarkers[0].Seconds
}) })
var result []*models.SceneMarkerTag var result []*SceneMarkerTag
for _, key := range keys { for _, key := range keys {
result = append(result, tags[key]) result = append(result, tags[key])
} }

View file

@ -33,12 +33,12 @@ func (r *imageResolver) File(ctx context.Context, obj *models.Image) (*models.Im
}, nil }, nil
} }
func (r *imageResolver) Paths(ctx context.Context, obj *models.Image) (*models.ImagePathsType, error) { func (r *imageResolver) Paths(ctx context.Context, obj *models.Image) (*ImagePathsType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string) baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
builder := urlbuilders.NewImageURLBuilder(baseURL, obj) builder := urlbuilders.NewImageURLBuilder(baseURL, obj)
thumbnailPath := builder.GetThumbnailURL() thumbnailPath := builder.GetThumbnailURL()
imagePath := builder.GetImageURL() imagePath := builder.GetImageURL()
return &models.ImagePathsType{ return &ImagePathsType{
Image: &imagePath, Image: &imagePath,
Thumbnail: &thumbnailPath, Thumbnail: &thumbnailPath,
}, nil }, nil

View file

@ -85,7 +85,7 @@ func (r *sceneResolver) File(ctx context.Context, obj *models.Scene) (*models.Sc
}, nil }, nil
} }
func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*models.ScenePathsType, error) { func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*ScenePathsType, error) {
baseURL, _ := ctx.Value(BaseURLCtxKey).(string) baseURL, _ := ctx.Value(BaseURLCtxKey).(string)
config := manager.GetInstance().Config config := manager.GetInstance().Config
builder := urlbuilders.NewSceneURLBuilder(baseURL, obj.ID) builder := urlbuilders.NewSceneURLBuilder(baseURL, obj.ID)
@ -101,7 +101,7 @@ func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*models.S
captionBasePath := builder.GetCaptionURL() captionBasePath := builder.GetCaptionURL()
interactiveHeatmap := builder.GetInteractiveHeatmapURL() interactiveHeatmap := builder.GetInteractiveHeatmapURL()
return &models.ScenePathsType{ return &ScenePathsType{
Screenshot: &screenshotPath, Screenshot: &screenshotPath,
Preview: &previewPath, Preview: &previewPath,
Stream: &streamPath, Stream: &streamPath,
@ -163,7 +163,7 @@ func (r *sceneResolver) Studio(ctx context.Context, obj *models.Scene) (ret *mod
return ret, nil return ret, nil
} }
func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*models.SceneMovie, err error) { func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*SceneMovie, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
qb := repo.Scene() qb := repo.Scene()
mqb := repo.Movie() mqb := repo.Movie()
@ -180,7 +180,7 @@ func (r *sceneResolver) Movies(ctx context.Context, obj *models.Scene) (ret []*m
} }
sceneIdx := sm.SceneIndex sceneIdx := sm.SceneIndex
sceneMovie := &models.SceneMovie{ sceneMovie := &SceneMovie{
Movie: movie, Movie: movie,
} }
@ -252,7 +252,7 @@ func (r *sceneResolver) FileModTime(ctx context.Context, obj *models.Scene) (*ti
return &obj.FileModTime.Timestamp, nil return &obj.FileModTime.Timestamp, nil
} }
func (r *sceneResolver) SceneStreams(ctx context.Context, obj *models.Scene) ([]*models.SceneStreamEndpoint, error) { func (r *sceneResolver) SceneStreams(ctx context.Context, obj *models.Scene) ([]*manager.SceneStreamEndpoint, error) {
config := manager.GetInstance().Config config := manager.GetInstance().Config
baseURL, _ := ctx.Value(BaseURLCtxKey).(string) baseURL, _ := ctx.Value(BaseURLCtxKey).(string)

View file

@ -15,17 +15,17 @@ import (
var ErrOverriddenConfig = errors.New("cannot set overridden value") var ErrOverriddenConfig = errors.New("cannot set overridden value")
func (r *mutationResolver) Setup(ctx context.Context, input models.SetupInput) (bool, error) { func (r *mutationResolver) Setup(ctx context.Context, input manager.SetupInput) (bool, error) {
err := manager.GetInstance().Setup(ctx, input) err := manager.GetInstance().Setup(ctx, input)
return err == nil, err return err == nil, err
} }
func (r *mutationResolver) Migrate(ctx context.Context, input models.MigrateInput) (bool, error) { func (r *mutationResolver) Migrate(ctx context.Context, input manager.MigrateInput) (bool, error) {
err := manager.GetInstance().Migrate(ctx, input) err := manager.GetInstance().Migrate(ctx, input)
return err == nil, err return err == nil, err
} }
func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.ConfigGeneralInput) (*models.ConfigGeneralResult, error) { func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGeneralInput) (*ConfigGeneralResult, error) {
c := config.GetInstance() c := config.GetInstance()
existingPaths := c.GetStashPaths() existingPaths := c.GetStashPaths()
@ -281,7 +281,7 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
return makeConfigGeneralResult(), nil return makeConfigGeneralResult(), nil
} }
func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.ConfigInterfaceInput) (*models.ConfigInterfaceResult, error) { func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigInterfaceInput) (*ConfigInterfaceResult, error) {
c := config.GetInstance() c := config.GetInstance()
setBool := func(key string, v *bool) { setBool := func(key string, v *bool) {
@ -338,10 +338,10 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
c.Set(config.ImageLightboxSlideshowDelay, *options.SlideshowDelay) c.Set(config.ImageLightboxSlideshowDelay, *options.SlideshowDelay)
} }
setString(config.ImageLightboxDisplayMode, (*string)(options.DisplayMode)) setString(config.ImageLightboxDisplayModeKey, (*string)(options.DisplayMode))
setBool(config.ImageLightboxScaleUp, options.ScaleUp) setBool(config.ImageLightboxScaleUp, options.ScaleUp)
setBool(config.ImageLightboxResetZoomOnNav, options.ResetZoomOnNav) setBool(config.ImageLightboxResetZoomOnNav, options.ResetZoomOnNav)
setString(config.ImageLightboxScrollMode, (*string)(options.ScrollMode)) setString(config.ImageLightboxScrollModeKey, (*string)(options.ScrollMode))
if options.ScrollAttemptsBeforeChange != nil { if options.ScrollAttemptsBeforeChange != nil {
c.Set(config.ImageLightboxScrollAttemptsBeforeChange, *options.ScrollAttemptsBeforeChange) c.Set(config.ImageLightboxScrollAttemptsBeforeChange, *options.ScrollAttemptsBeforeChange)
@ -376,7 +376,7 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
return makeConfigInterfaceResult(), nil return makeConfigInterfaceResult(), nil
} }
func (r *mutationResolver) ConfigureDlna(ctx context.Context, input models.ConfigDLNAInput) (*models.ConfigDLNAResult, error) { func (r *mutationResolver) ConfigureDlna(ctx context.Context, input ConfigDLNAInput) (*ConfigDLNAResult, error) {
c := config.GetInstance() c := config.GetInstance()
if input.ServerName != nil { if input.ServerName != nil {
@ -413,7 +413,7 @@ func (r *mutationResolver) ConfigureDlna(ctx context.Context, input models.Confi
return makeConfigDLNAResult(), nil return makeConfigDLNAResult(), nil
} }
func (r *mutationResolver) ConfigureScraping(ctx context.Context, input models.ConfigScrapingInput) (*models.ConfigScrapingResult, error) { func (r *mutationResolver) ConfigureScraping(ctx context.Context, input ConfigScrapingInput) (*ConfigScrapingResult, error) {
c := config.GetInstance() c := config.GetInstance()
refreshScraperCache := false refreshScraperCache := false
@ -445,7 +445,7 @@ func (r *mutationResolver) ConfigureScraping(ctx context.Context, input models.C
return makeConfigScrapingResult(), nil return makeConfigScrapingResult(), nil
} }
func (r *mutationResolver) ConfigureDefaults(ctx context.Context, input models.ConfigDefaultSettingsInput) (*models.ConfigDefaultSettingsResult, error) { func (r *mutationResolver) ConfigureDefaults(ctx context.Context, input ConfigDefaultSettingsInput) (*ConfigDefaultSettingsResult, error) {
c := config.GetInstance() c := config.GetInstance()
if input.Identify != nil { if input.Identify != nil {
@ -479,7 +479,7 @@ func (r *mutationResolver) ConfigureDefaults(ctx context.Context, input models.C
return makeConfigDefaultsResult(), nil return makeConfigDefaultsResult(), nil
} }
func (r *mutationResolver) GenerateAPIKey(ctx context.Context, input models.GenerateAPIKeyInput) (string, error) { func (r *mutationResolver) GenerateAPIKey(ctx context.Context, input GenerateAPIKeyInput) (string, error) {
c := config.GetInstance() c := config.GetInstance()
var newAPIKey string var newAPIKey string

View file

@ -5,10 +5,9 @@ import (
"time" "time"
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
) )
func (r *mutationResolver) EnableDlna(ctx context.Context, input models.EnableDLNAInput) (bool, error) { func (r *mutationResolver) EnableDlna(ctx context.Context, input EnableDLNAInput) (bool, error) {
err := manager.GetInstance().DLNAService.Start(parseMinutes(input.Duration)) err := manager.GetInstance().DLNAService.Start(parseMinutes(input.Duration))
if err != nil { if err != nil {
return false, err return false, err
@ -16,17 +15,17 @@ func (r *mutationResolver) EnableDlna(ctx context.Context, input models.EnableDL
return true, nil return true, nil
} }
func (r *mutationResolver) DisableDlna(ctx context.Context, input models.DisableDLNAInput) (bool, error) { func (r *mutationResolver) DisableDlna(ctx context.Context, input DisableDLNAInput) (bool, error) {
manager.GetInstance().DLNAService.Stop(parseMinutes(input.Duration)) manager.GetInstance().DLNAService.Stop(parseMinutes(input.Duration))
return true, nil return true, nil
} }
func (r *mutationResolver) AddTempDlnaip(ctx context.Context, input models.AddTempDLNAIPInput) (bool, error) { func (r *mutationResolver) AddTempDlnaip(ctx context.Context, input AddTempDLNAIPInput) (bool, error) {
manager.GetInstance().DLNAService.AddTempDLNAIP(input.Address, parseMinutes(input.Duration)) manager.GetInstance().DLNAService.AddTempDLNAIP(input.Address, parseMinutes(input.Duration))
return true, nil return true, nil
} }
func (r *mutationResolver) RemoveTempDlnaip(ctx context.Context, input models.RemoveTempDLNAIPInput) (bool, error) { func (r *mutationResolver) RemoveTempDlnaip(ctx context.Context, input RemoveTempDLNAIPInput) (bool, error) {
ret := manager.GetInstance().DLNAService.RemoveTempDLNAIP(input.Address) ret := manager.GetInstance().DLNAService.RemoveTempDLNAIP(input.Address)
return ret, nil return ret, nil
} }

View file

@ -31,7 +31,7 @@ func (r *mutationResolver) getGallery(ctx context.Context, id int) (ret *models.
return ret, nil return ret, nil
} }
func (r *mutationResolver) GalleryCreate(ctx context.Context, input models.GalleryCreateInput) (*models.Gallery, error) { func (r *mutationResolver) GalleryCreate(ctx context.Context, input GalleryCreateInput) (*models.Gallery, error) {
// name must be provided // name must be provided
if input.Title == "" { if input.Title == "" {
return nil, errors.New("title must not be empty") return nil, errors.New("title must not be empty")
@ -273,7 +273,7 @@ func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, transl
return gallery, nil return gallery, nil
} }
func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.BulkGalleryUpdateInput) ([]*models.Gallery, error) { func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input BulkGalleryUpdateInput) ([]*models.Gallery, error) {
// Populate gallery from the input // Populate gallery from the input
updatedTime := time.Now() updatedTime := time.Now()
@ -367,7 +367,7 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.B
return newRet, nil return newRet, nil
} }
func adjustGalleryPerformerIDs(qb models.GalleryReader, galleryID int, ids models.BulkUpdateIds) (ret []int, err error) { func adjustGalleryPerformerIDs(qb models.GalleryReader, galleryID int, ids BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetPerformerIDs(galleryID) ret, err = qb.GetPerformerIDs(galleryID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -376,7 +376,7 @@ func adjustGalleryPerformerIDs(qb models.GalleryReader, galleryID int, ids model
return adjustIDs(ret, ids), nil return adjustIDs(ret, ids), nil
} }
func adjustGalleryTagIDs(qb models.GalleryReader, galleryID int, ids models.BulkUpdateIds) (ret []int, err error) { func adjustGalleryTagIDs(qb models.GalleryReader, galleryID int, ids BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetTagIDs(galleryID) ret, err = qb.GetTagIDs(galleryID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -385,7 +385,7 @@ 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) { func adjustGallerySceneIDs(qb models.GalleryReader, galleryID int, ids BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetSceneIDs(galleryID) ret, err = qb.GetSceneIDs(galleryID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -526,7 +526,7 @@ func isStashPath(path string) bool {
return false return false
} }
func (r *mutationResolver) AddGalleryImages(ctx context.Context, input models.GalleryAddInput) (bool, error) { func (r *mutationResolver) AddGalleryImages(ctx context.Context, input GalleryAddInput) (bool, error) {
galleryID, err := strconv.Atoi(input.GalleryID) galleryID, err := strconv.Atoi(input.GalleryID)
if err != nil { if err != nil {
return false, err return false, err
@ -566,7 +566,7 @@ func (r *mutationResolver) AddGalleryImages(ctx context.Context, input models.Ga
return true, nil return true, nil
} }
func (r *mutationResolver) RemoveGalleryImages(ctx context.Context, input models.GalleryRemoveInput) (bool, error) { func (r *mutationResolver) RemoveGalleryImages(ctx context.Context, input GalleryRemoveInput) (bool, error) {
galleryID, err := strconv.Atoi(input.GalleryID) galleryID, err := strconv.Atoi(input.GalleryID)
if err != nil { if err != nil {
return false, err return false, err

View file

@ -26,7 +26,7 @@ func (r *mutationResolver) getImage(ctx context.Context, id int) (ret *models.Im
return ret, nil return ret, nil
} }
func (r *mutationResolver) ImageUpdate(ctx context.Context, input models.ImageUpdateInput) (ret *models.Image, err error) { func (r *mutationResolver) ImageUpdate(ctx context.Context, input ImageUpdateInput) (ret *models.Image, err error) {
translator := changesetTranslator{ translator := changesetTranslator{
inputMap: getUpdateInputMap(ctx), inputMap: getUpdateInputMap(ctx),
} }
@ -44,7 +44,7 @@ func (r *mutationResolver) ImageUpdate(ctx context.Context, input models.ImageUp
return r.getImage(ctx, ret.ID) return r.getImage(ctx, ret.ID)
} }
func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*models.ImageUpdateInput) (ret []*models.Image, err error) { func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*ImageUpdateInput) (ret []*models.Image, err error) {
inputMaps := getUpdateInputMaps(ctx) inputMaps := getUpdateInputMaps(ctx)
// Start the transaction and save the image // Start the transaction and save the image
@ -86,7 +86,7 @@ func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*models.Ima
return newRet, nil return newRet, nil
} }
func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, translator changesetTranslator, repo models.Repository) (*models.Image, error) { func (r *mutationResolver) imageUpdate(input ImageUpdateInput, translator changesetTranslator, repo models.Repository) (*models.Image, error) {
// Populate image from the input // Populate image from the input
imageID, err := strconv.Atoi(input.ID) imageID, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
@ -157,7 +157,7 @@ func (r *mutationResolver) updateImageTags(qb models.ImageReaderWriter, imageID
return qb.UpdateTags(imageID, ids) return qb.UpdateTags(imageID, ids)
} }
func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.BulkImageUpdateInput) (ret []*models.Image, err error) { func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input BulkImageUpdateInput) (ret []*models.Image, err error) {
imageIDs, err := stringslice.StringSliceToIntSlice(input.Ids) imageIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil { if err != nil {
return nil, err return nil, err
@ -251,7 +251,7 @@ func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.Bul
return newRet, nil return newRet, nil
} }
func adjustImageGalleryIDs(qb models.ImageReader, imageID int, ids models.BulkUpdateIds) (ret []int, err error) { func adjustImageGalleryIDs(qb models.ImageReader, imageID int, ids BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetGalleryIDs(imageID) ret, err = qb.GetGalleryIDs(imageID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -260,7 +260,7 @@ func adjustImageGalleryIDs(qb models.ImageReader, imageID int, ids models.BulkUp
return adjustIDs(ret, ids), nil return adjustIDs(ret, ids), nil
} }
func adjustImagePerformerIDs(qb models.ImageReader, imageID int, ids models.BulkUpdateIds) (ret []int, err error) { func adjustImagePerformerIDs(qb models.ImageReader, imageID int, ids BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetPerformerIDs(imageID) ret, err = qb.GetPerformerIDs(imageID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -269,7 +269,7 @@ func adjustImagePerformerIDs(qb models.ImageReader, imageID int, ids models.Bulk
return adjustIDs(ret, ids), nil return adjustIDs(ret, ids), nil
} }
func adjustImageTagIDs(qb models.ImageReader, imageID int, ids models.BulkUpdateIds) (ret []int, err error) { func adjustImageTagIDs(qb models.ImageReader, imageID int, ids BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetTagIDs(imageID) ret, err = qb.GetTagIDs(imageID)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -9,15 +9,15 @@ import (
"sync" "sync"
"time" "time"
"github.com/stashapp/stash/internal/identify"
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
) )
func (r *mutationResolver) MetadataScan(ctx context.Context, input models.ScanMetadataInput) (string, error) { func (r *mutationResolver) MetadataScan(ctx context.Context, input manager.ScanMetadataInput) (string, error) {
jobID, err := manager.GetInstance().Scan(ctx, input) jobID, err := manager.GetInstance().Scan(ctx, input)
if err != nil { if err != nil {
@ -36,7 +36,7 @@ func (r *mutationResolver) MetadataImport(ctx context.Context) (string, error) {
return strconv.Itoa(jobID), nil return strconv.Itoa(jobID), nil
} }
func (r *mutationResolver) ImportObjects(ctx context.Context, input models.ImportObjectsInput) (string, error) { func (r *mutationResolver) ImportObjects(ctx context.Context, input manager.ImportObjectsInput) (string, error) {
t, err := manager.CreateImportTask(config.GetInstance().GetVideoFileNamingAlgorithm(), input) t, err := manager.CreateImportTask(config.GetInstance().GetVideoFileNamingAlgorithm(), input)
if err != nil { if err != nil {
return "", err return "", err
@ -56,7 +56,7 @@ func (r *mutationResolver) MetadataExport(ctx context.Context) (string, error) {
return strconv.Itoa(jobID), nil return strconv.Itoa(jobID), nil
} }
func (r *mutationResolver) ExportObjects(ctx context.Context, input models.ExportObjectsInput) (*string, error) { func (r *mutationResolver) ExportObjects(ctx context.Context, input manager.ExportObjectsInput) (*string, error) {
t := manager.CreateExportTask(config.GetInstance().GetVideoFileNamingAlgorithm(), input) t := manager.CreateExportTask(config.GetInstance().GetVideoFileNamingAlgorithm(), input)
var wg sync.WaitGroup var wg sync.WaitGroup
@ -75,7 +75,7 @@ func (r *mutationResolver) ExportObjects(ctx context.Context, input models.Expor
return nil, nil return nil, nil
} }
func (r *mutationResolver) MetadataGenerate(ctx context.Context, input models.GenerateMetadataInput) (string, error) { func (r *mutationResolver) MetadataGenerate(ctx context.Context, input manager.GenerateMetadataInput) (string, error) {
jobID, err := manager.GetInstance().Generate(ctx, input) jobID, err := manager.GetInstance().Generate(ctx, input)
if err != nil { if err != nil {
@ -85,19 +85,19 @@ func (r *mutationResolver) MetadataGenerate(ctx context.Context, input models.Ge
return strconv.Itoa(jobID), nil return strconv.Itoa(jobID), nil
} }
func (r *mutationResolver) MetadataAutoTag(ctx context.Context, input models.AutoTagMetadataInput) (string, error) { func (r *mutationResolver) MetadataAutoTag(ctx context.Context, input manager.AutoTagMetadataInput) (string, error) {
jobID := manager.GetInstance().AutoTag(ctx, input) jobID := manager.GetInstance().AutoTag(ctx, input)
return strconv.Itoa(jobID), nil return strconv.Itoa(jobID), nil
} }
func (r *mutationResolver) MetadataIdentify(ctx context.Context, input models.IdentifyMetadataInput) (string, error) { func (r *mutationResolver) MetadataIdentify(ctx context.Context, input identify.Options) (string, error) {
t := manager.CreateIdentifyJob(input) t := manager.CreateIdentifyJob(input)
jobID := manager.GetInstance().JobManager.Add(ctx, "Identifying...", t) jobID := manager.GetInstance().JobManager.Add(ctx, "Identifying...", t)
return strconv.Itoa(jobID), nil return strconv.Itoa(jobID), nil
} }
func (r *mutationResolver) MetadataClean(ctx context.Context, input models.CleanMetadataInput) (string, error) { func (r *mutationResolver) MetadataClean(ctx context.Context, input manager.CleanMetadataInput) (string, error) {
jobID := manager.GetInstance().Clean(ctx, input) jobID := manager.GetInstance().Clean(ctx, input)
return strconv.Itoa(jobID), nil return strconv.Itoa(jobID), nil
} }
@ -107,7 +107,7 @@ func (r *mutationResolver) MigrateHashNaming(ctx context.Context) (string, error
return strconv.Itoa(jobID), nil return strconv.Itoa(jobID), nil
} }
func (r *mutationResolver) BackupDatabase(ctx context.Context, input models.BackupDatabaseInput) (*string, error) { func (r *mutationResolver) BackupDatabase(ctx context.Context, input BackupDatabaseInput) (*string, error) {
// if download is true, then backup to temporary file and return a link // if download is true, then backup to temporary file and return a link
download := input.Download != nil && *input.Download download := input.Download != nil && *input.Download
mgr := manager.GetInstance() mgr := manager.GetInstance()

View file

@ -25,7 +25,7 @@ func (r *mutationResolver) getMovie(ctx context.Context, id int) (ret *models.Mo
return ret, nil return ret, nil
} }
func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCreateInput) (*models.Movie, error) { func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInput) (*models.Movie, error) {
// generate checksum from movie name rather than image // generate checksum from movie name rather than image
checksum := md5.FromString(input.Name) checksum := md5.FromString(input.Name)
@ -123,7 +123,7 @@ func (r *mutationResolver) MovieCreate(ctx context.Context, input models.MovieCr
return r.getMovie(ctx, movie.ID) return r.getMovie(ctx, movie.ID)
} }
func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUpdateInput) (*models.Movie, error) { func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInput) (*models.Movie, error) {
// Populate movie from the input // Populate movie from the input
movieID, err := strconv.Atoi(input.ID) movieID, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
@ -223,7 +223,7 @@ func (r *mutationResolver) MovieUpdate(ctx context.Context, input models.MovieUp
return r.getMovie(ctx, movie.ID) return r.getMovie(ctx, movie.ID)
} }
func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input models.BulkMovieUpdateInput) ([]*models.Movie, error) { func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieUpdateInput) ([]*models.Movie, error) {
movieIDs, err := stringslice.StringSliceToIntSlice(input.Ids) movieIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil { if err != nil {
return nil, err return nil, err
@ -288,7 +288,7 @@ func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input models.Bul
return newRet, nil return newRet, nil
} }
func (r *mutationResolver) MovieDestroy(ctx context.Context, input models.MovieDestroyInput) (bool, error) { func (r *mutationResolver) MovieDestroy(ctx context.Context, input MovieDestroyInput) (bool, error) {
id, err := strconv.Atoi(input.ID) id, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
return false, err return false, err

View file

@ -26,7 +26,7 @@ func (r *mutationResolver) getPerformer(ctx context.Context, id int) (ret *model
return ret, nil return ret, nil
} }
func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.PerformerCreateInput) (*models.Performer, error) { func (r *mutationResolver) PerformerCreate(ctx context.Context, input PerformerCreateInput) (*models.Performer, error) {
// generate checksum from performer name rather than image // generate checksum from performer name rather than image
checksum := md5.FromString(input.Name) checksum := md5.FromString(input.Name)
@ -167,7 +167,7 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per
return r.getPerformer(ctx, performer.ID) return r.getPerformer(ctx, performer.ID)
} }
func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.PerformerUpdateInput) (*models.Performer, error) { func (r *mutationResolver) PerformerUpdate(ctx context.Context, input PerformerUpdateInput) (*models.Performer, error) {
// Populate performer from the input // Populate performer from the input
performerID, _ := strconv.Atoi(input.ID) performerID, _ := strconv.Atoi(input.ID)
updatedPerformer := models.PerformerPartial{ updatedPerformer := models.PerformerPartial{
@ -298,7 +298,7 @@ func (r *mutationResolver) updatePerformerTags(qb models.PerformerReaderWriter,
return qb.UpdateTags(performerID, ids) return qb.UpdateTags(performerID, ids)
} }
func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input models.BulkPerformerUpdateInput) ([]*models.Performer, error) { func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPerformerUpdateInput) ([]*models.Performer, error) {
performerIDs, err := stringslice.StringSliceToIntSlice(input.Ids) performerIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil { if err != nil {
return nil, err return nil, err
@ -409,7 +409,7 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input models
return newRet, nil return newRet, nil
} }
func (r *mutationResolver) PerformerDestroy(ctx context.Context, input models.PerformerDestroyInput) (bool, error) { func (r *mutationResolver) PerformerDestroy(ctx context.Context, input PerformerDestroyInput) (bool, error) {
id, err := strconv.Atoi(input.ID) id, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
return false, err return false, err

View file

@ -5,10 +5,10 @@ import (
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/plugin"
) )
func (r *mutationResolver) RunPluginTask(ctx context.Context, pluginID string, taskName string, args []*models.PluginArgInput) (string, error) { func (r *mutationResolver) RunPluginTask(ctx context.Context, pluginID string, taskName string, args []*plugin.PluginArgInput) (string, error) {
m := manager.GetInstance() m := manager.GetInstance()
m.RunPluginTask(ctx, pluginID, taskName, args) m.RunPluginTask(ctx, pluginID, taskName, args)
return "todo", nil return "todo", nil

View file

@ -9,7 +9,7 @@ import (
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
) )
func (r *mutationResolver) SaveFilter(ctx context.Context, input models.SaveFilterInput) (ret *models.SavedFilter, err error) { func (r *mutationResolver) SaveFilter(ctx context.Context, input SaveFilterInput) (ret *models.SavedFilter, err error) {
if strings.TrimSpace(input.Name) == "" { if strings.TrimSpace(input.Name) == "" {
return nil, errors.New("name must be non-empty") return nil, errors.New("name must be non-empty")
} }
@ -42,7 +42,7 @@ func (r *mutationResolver) SaveFilter(ctx context.Context, input models.SaveFilt
return ret, err return ret, err
} }
func (r *mutationResolver) DestroySavedFilter(ctx context.Context, input models.DestroyFilterInput) (bool, error) { func (r *mutationResolver) DestroySavedFilter(ctx context.Context, input DestroyFilterInput) (bool, error) {
id, err := strconv.Atoi(input.ID) id, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
return false, err return false, err
@ -57,7 +57,7 @@ func (r *mutationResolver) DestroySavedFilter(ctx context.Context, input models.
return true, nil return true, nil
} }
func (r *mutationResolver) SetDefaultFilter(ctx context.Context, input models.SetDefaultFilterInput) (bool, error) { func (r *mutationResolver) SetDefaultFilter(ctx context.Context, input SetDefaultFilterInput) (bool, error) {
if err := r.withTxn(ctx, func(repo models.Repository) error { if err := r.withTxn(ctx, func(repo models.Repository) error {
qb := repo.SavedFilter() qb := repo.SavedFilter()

View file

@ -232,7 +232,7 @@ func (r *mutationResolver) updateSceneGalleries(qb models.SceneReaderWriter, sce
return qb.UpdateGalleries(sceneID, ids) 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 BulkSceneUpdateInput) ([]*models.Scene, error) {
sceneIDs, err := stringslice.StringSliceToIntSlice(input.Ids) sceneIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
if err != nil { if err != nil {
return nil, err return nil, err
@ -343,9 +343,9 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input models.Bul
return newRet, nil return newRet, nil
} }
func adjustIDs(existingIDs []int, updateIDs models.BulkUpdateIds) []int { func adjustIDs(existingIDs []int, updateIDs BulkUpdateIds) []int {
// if we are setting the ids, just return the ids // if we are setting the ids, just return the ids
if updateIDs.Mode == models.BulkUpdateIDModeSet { if updateIDs.Mode == BulkUpdateIDModeSet {
existingIDs = []int{} existingIDs = []int{}
for _, idStr := range updateIDs.Ids { for _, idStr := range updateIDs.Ids {
id, _ := strconv.Atoi(idStr) id, _ := strconv.Atoi(idStr)
@ -362,7 +362,7 @@ func adjustIDs(existingIDs []int, updateIDs models.BulkUpdateIds) []int {
foundExisting := false foundExisting := false
for idx, existingID := range existingIDs { for idx, existingID := range existingIDs {
if existingID == id { if existingID == id {
if updateIDs.Mode == models.BulkUpdateIDModeRemove { if updateIDs.Mode == BulkUpdateIDModeRemove {
// remove from the list // remove from the list
existingIDs = append(existingIDs[:idx], existingIDs[idx+1:]...) existingIDs = append(existingIDs[:idx], existingIDs[idx+1:]...)
} }
@ -372,7 +372,7 @@ func adjustIDs(existingIDs []int, updateIDs models.BulkUpdateIds) []int {
} }
} }
if !foundExisting && updateIDs.Mode != models.BulkUpdateIDModeRemove { if !foundExisting && updateIDs.Mode != BulkUpdateIDModeRemove {
existingIDs = append(existingIDs, id) existingIDs = append(existingIDs, id)
} }
} }
@ -380,7 +380,7 @@ func adjustIDs(existingIDs []int, updateIDs models.BulkUpdateIds) []int {
return existingIDs return existingIDs
} }
func adjustScenePerformerIDs(qb models.SceneReader, sceneID int, ids models.BulkUpdateIds) (ret []int, err error) { func adjustScenePerformerIDs(qb models.SceneReader, sceneID int, ids BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetPerformerIDs(sceneID) ret, err = qb.GetPerformerIDs(sceneID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -393,7 +393,7 @@ type tagIDsGetter interface {
GetTagIDs(id int) ([]int, error) GetTagIDs(id int) ([]int, error)
} }
func adjustTagIDs(qb tagIDsGetter, sceneID int, ids models.BulkUpdateIds) (ret []int, err error) { func adjustTagIDs(qb tagIDsGetter, sceneID int, ids BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetTagIDs(sceneID) ret, err = qb.GetTagIDs(sceneID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -402,7 +402,7 @@ func adjustTagIDs(qb tagIDsGetter, sceneID int, ids models.BulkUpdateIds) (ret [
return adjustIDs(ret, ids), nil return adjustIDs(ret, ids), nil
} }
func adjustSceneGalleryIDs(qb models.SceneReader, sceneID int, ids models.BulkUpdateIds) (ret []int, err error) { func adjustSceneGalleryIDs(qb models.SceneReader, sceneID int, ids BulkUpdateIds) (ret []int, err error) {
ret, err = qb.GetGalleryIDs(sceneID) ret, err = qb.GetGalleryIDs(sceneID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -411,14 +411,14 @@ func adjustSceneGalleryIDs(qb models.SceneReader, sceneID int, ids models.BulkUp
return adjustIDs(ret, ids), nil return adjustIDs(ret, ids), nil
} }
func adjustSceneMovieIDs(qb models.SceneReader, sceneID int, updateIDs models.BulkUpdateIds) ([]models.MoviesScenes, error) { func adjustSceneMovieIDs(qb models.SceneReader, sceneID int, updateIDs BulkUpdateIds) ([]models.MoviesScenes, error) {
existingMovies, err := qb.GetMovies(sceneID) existingMovies, err := qb.GetMovies(sceneID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// if we are setting the ids, just return the ids // if we are setting the ids, just return the ids
if updateIDs.Mode == models.BulkUpdateIDModeSet { if updateIDs.Mode == BulkUpdateIDModeSet {
existingMovies = []models.MoviesScenes{} existingMovies = []models.MoviesScenes{}
for _, idStr := range updateIDs.Ids { for _, idStr := range updateIDs.Ids {
id, _ := strconv.Atoi(idStr) id, _ := strconv.Atoi(idStr)
@ -435,7 +435,7 @@ func adjustSceneMovieIDs(qb models.SceneReader, sceneID int, updateIDs models.Bu
foundExisting := false foundExisting := false
for idx, existingMovie := range existingMovies { for idx, existingMovie := range existingMovies {
if existingMovie.MovieID == id { if existingMovie.MovieID == id {
if updateIDs.Mode == models.BulkUpdateIDModeRemove { if updateIDs.Mode == BulkUpdateIDModeRemove {
// remove from the list // remove from the list
existingMovies = append(existingMovies[:idx], existingMovies[idx+1:]...) existingMovies = append(existingMovies[:idx], existingMovies[idx+1:]...)
} }
@ -445,7 +445,7 @@ func adjustSceneMovieIDs(qb models.SceneReader, sceneID int, updateIDs models.Bu
} }
} }
if !foundExisting && updateIDs.Mode != models.BulkUpdateIDModeRemove { if !foundExisting && updateIDs.Mode != BulkUpdateIDModeRemove {
existingMovies = append(existingMovies, models.MoviesScenes{MovieID: id}) existingMovies = append(existingMovies, models.MoviesScenes{MovieID: id})
} }
} }
@ -574,7 +574,7 @@ func (r *mutationResolver) getSceneMarker(ctx context.Context, id int) (ret *mod
return ret, nil return ret, nil
} }
func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input models.SceneMarkerCreateInput) (*models.SceneMarker, error) { func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input SceneMarkerCreateInput) (*models.SceneMarker, error) {
primaryTagID, err := strconv.Atoi(input.PrimaryTagID) primaryTagID, err := strconv.Atoi(input.PrimaryTagID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -609,7 +609,7 @@ func (r *mutationResolver) SceneMarkerCreate(ctx context.Context, input models.S
return r.getSceneMarker(ctx, ret.ID) return r.getSceneMarker(ctx, ret.ID)
} }
func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input models.SceneMarkerUpdateInput) (*models.SceneMarker, error) { func (r *mutationResolver) SceneMarkerUpdate(ctx context.Context, input SceneMarkerUpdateInput) (*models.SceneMarker, error) {
// Populate scene marker from the input // Populate scene marker from the input
sceneMarkerID, err := strconv.Atoi(input.ID) sceneMarkerID, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {

View file

@ -11,7 +11,7 @@ import (
"github.com/stashapp/stash/pkg/scraper/stashbox" "github.com/stashapp/stash/pkg/scraper/stashbox"
) )
func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input models.StashBoxFingerprintSubmissionInput) (bool, error) { func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input StashBoxFingerprintSubmissionInput) (bool, error) {
boxes := config.GetInstance().GetStashBoxes() boxes := config.GetInstance().GetStashBoxes()
if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) { if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) {
@ -23,12 +23,12 @@ func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input
return client.SubmitStashBoxFingerprints(ctx, input.SceneIds, boxes[input.StashBoxIndex].Endpoint) return client.SubmitStashBoxFingerprints(ctx, input.SceneIds, boxes[input.StashBoxIndex].Endpoint)
} }
func (r *mutationResolver) StashBoxBatchPerformerTag(ctx context.Context, input models.StashBoxBatchPerformerTagInput) (string, error) { func (r *mutationResolver) StashBoxBatchPerformerTag(ctx context.Context, input manager.StashBoxBatchPerformerTagInput) (string, error) {
jobID := manager.GetInstance().StashBoxBatchPerformerTag(ctx, input) jobID := manager.GetInstance().StashBoxBatchPerformerTag(ctx, input)
return strconv.Itoa(jobID), nil return strconv.Itoa(jobID), nil
} }
func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input models.StashBoxDraftSubmissionInput) (*string, error) { func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input StashBoxDraftSubmissionInput) (*string, error) {
boxes := config.GetInstance().GetStashBoxes() boxes := config.GetInstance().GetStashBoxes()
if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) { if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) {
@ -58,7 +58,7 @@ func (r *mutationResolver) SubmitStashBoxSceneDraft(ctx context.Context, input m
return res, err return res, err
} }
func (r *mutationResolver) SubmitStashBoxPerformerDraft(ctx context.Context, input models.StashBoxDraftSubmissionInput) (*string, error) { func (r *mutationResolver) SubmitStashBoxPerformerDraft(ctx context.Context, input StashBoxDraftSubmissionInput) (*string, error) {
boxes := config.GetInstance().GetStashBoxes() boxes := config.GetInstance().GetStashBoxes()
if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) { if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) {

View file

@ -27,7 +27,7 @@ func (r *mutationResolver) getStudio(ctx context.Context, id int) (ret *models.S
return ret, nil return ret, nil
} }
func (r *mutationResolver) StudioCreate(ctx context.Context, input models.StudioCreateInput) (*models.Studio, error) { func (r *mutationResolver) StudioCreate(ctx context.Context, input StudioCreateInput) (*models.Studio, error) {
// generate checksum from studio name rather than image // generate checksum from studio name rather than image
checksum := md5.FromString(input.Name) checksum := md5.FromString(input.Name)
@ -115,7 +115,7 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio
return r.getStudio(ctx, s.ID) return r.getStudio(ctx, s.ID)
} }
func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.StudioUpdateInput) (*models.Studio, error) { func (r *mutationResolver) StudioUpdate(ctx context.Context, input StudioUpdateInput) (*models.Studio, error) {
// Populate studio from the input // Populate studio from the input
studioID, err := strconv.Atoi(input.ID) studioID, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
@ -207,7 +207,7 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio
return r.getStudio(ctx, s.ID) return r.getStudio(ctx, s.ID)
} }
func (r *mutationResolver) StudioDestroy(ctx context.Context, input models.StudioDestroyInput) (bool, error) { func (r *mutationResolver) StudioDestroy(ctx context.Context, input StudioDestroyInput) (bool, error) {
id, err := strconv.Atoi(input.ID) id, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
return false, err return false, err

View file

@ -25,7 +25,7 @@ func (r *mutationResolver) getTag(ctx context.Context, id int) (ret *models.Tag,
return ret, nil return ret, nil
} }
func (r *mutationResolver) TagCreate(ctx context.Context, input models.TagCreateInput) (*models.Tag, error) { func (r *mutationResolver) TagCreate(ctx context.Context, input TagCreateInput) (*models.Tag, error) {
// Populate a new tag from the input // Populate a new tag from the input
currentTime := time.Now() currentTime := time.Now()
newTag := models.Tag{ newTag := models.Tag{
@ -127,7 +127,7 @@ func (r *mutationResolver) TagCreate(ctx context.Context, input models.TagCreate
return r.getTag(ctx, t.ID) return r.getTag(ctx, t.ID)
} }
func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdateInput) (*models.Tag, error) { func (r *mutationResolver) TagUpdate(ctx context.Context, input TagUpdateInput) (*models.Tag, error) {
// Populate tag from the input // Populate tag from the input
tagID, err := strconv.Atoi(input.ID) tagID, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
@ -252,7 +252,7 @@ func (r *mutationResolver) TagUpdate(ctx context.Context, input models.TagUpdate
return r.getTag(ctx, t.ID) return r.getTag(ctx, t.ID)
} }
func (r *mutationResolver) TagDestroy(ctx context.Context, input models.TagDestroyInput) (bool, error) { func (r *mutationResolver) TagDestroy(ctx context.Context, input TagDestroyInput) (bool, error) {
tagID, err := strconv.Atoi(input.ID) tagID, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
return false, err return false, err
@ -295,7 +295,7 @@ func (r *mutationResolver) TagsDestroy(ctx context.Context, tagIDs []string) (bo
return true, nil return true, nil
} }
func (r *mutationResolver) TagsMerge(ctx context.Context, input models.TagsMergeInput) (*models.Tag, error) { func (r *mutationResolver) TagsMerge(ctx context.Context, input TagsMergeInput) (*models.Tag, error) {
source, err := stringslice.StringSliceToIntSlice(input.Source) source, err := stringslice.StringSliceToIntSlice(input.Source)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -73,13 +73,13 @@ func TestTagCreate(t *testing.T) {
expectedErr := errors.New("TagCreate error") expectedErr := errors.New("TagCreate error")
tagRW.On("Create", mock.AnythingOfType("models.Tag")).Return(nil, expectedErr) tagRW.On("Create", mock.AnythingOfType("models.Tag")).Return(nil, expectedErr)
_, err := r.Mutation().TagCreate(context.TODO(), models.TagCreateInput{ _, err := r.Mutation().TagCreate(context.TODO(), TagCreateInput{
Name: existingTagName, Name: existingTagName,
}) })
assert.NotNil(t, err) assert.NotNil(t, err)
_, err = r.Mutation().TagCreate(context.TODO(), models.TagCreateInput{ _, err = r.Mutation().TagCreate(context.TODO(), TagCreateInput{
Name: errTagName, Name: errTagName,
}) })
@ -98,7 +98,7 @@ func TestTagCreate(t *testing.T) {
tagRW.On("Create", mock.AnythingOfType("models.Tag")).Return(newTag, nil) tagRW.On("Create", mock.AnythingOfType("models.Tag")).Return(newTag, nil)
tagRW.On("Find", newTagID).Return(newTag, nil) tagRW.On("Find", newTagID).Return(newTag, nil)
tag, err := r.Mutation().TagCreate(context.TODO(), models.TagCreateInput{ tag, err := r.Mutation().TagCreate(context.TODO(), TagCreateInput{
Name: tagName, Name: tagName,
}) })

View file

@ -13,13 +13,13 @@ import (
"golang.org/x/text/collate" "golang.org/x/text/collate"
) )
func (r *queryResolver) Configuration(ctx context.Context) (*models.ConfigResult, error) { func (r *queryResolver) Configuration(ctx context.Context) (*ConfigResult, error) {
return makeConfigResult(), nil return makeConfigResult(), nil
} }
func (r *queryResolver) Directory(ctx context.Context, path, locale *string) (*models.Directory, error) { func (r *queryResolver) Directory(ctx context.Context, path, locale *string) (*Directory, error) {
directory := &models.Directory{} directory := &Directory{}
var err error var err error
col := newCollator(locale, collate.IgnoreCase, collate.Numeric) col := newCollator(locale, collate.IgnoreCase, collate.Numeric)
@ -59,8 +59,8 @@ func getParent(path string) *string {
} }
} }
func makeConfigResult() *models.ConfigResult { func makeConfigResult() *ConfigResult {
return &models.ConfigResult{ return &ConfigResult{
General: makeConfigGeneralResult(), General: makeConfigGeneralResult(),
Interface: makeConfigInterfaceResult(), Interface: makeConfigInterfaceResult(),
Dlna: makeConfigDLNAResult(), Dlna: makeConfigDLNAResult(),
@ -70,7 +70,7 @@ func makeConfigResult() *models.ConfigResult {
} }
} }
func makeConfigGeneralResult() *models.ConfigGeneralResult { func makeConfigGeneralResult() *ConfigGeneralResult {
config := config.GetInstance() config := config.GetInstance()
logFile := config.GetLogFile() logFile := config.GetLogFile()
@ -82,7 +82,7 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult {
scraperUserAgent := config.GetScraperUserAgent() scraperUserAgent := config.GetScraperUserAgent()
scraperCDPPath := config.GetScraperCDPPath() scraperCDPPath := config.GetScraperCDPPath()
return &models.ConfigGeneralResult{ return &ConfigGeneralResult{
Stashes: config.GetStashPaths(), Stashes: config.GetStashPaths(),
DatabasePath: config.GetDatabasePath(), DatabasePath: config.GetDatabasePath(),
GeneratedPath: config.GetGeneratedPath(), GeneratedPath: config.GetGeneratedPath(),
@ -125,7 +125,7 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult {
} }
} }
func makeConfigInterfaceResult() *models.ConfigInterfaceResult { func makeConfigInterfaceResult() *ConfigInterfaceResult {
config := config.GetInstance() config := config.GetInstance()
menuItems := config.GetMenuItems() menuItems := config.GetMenuItems()
soundOnPreview := config.GetSoundOnPreview() soundOnPreview := config.GetSoundOnPreview()
@ -149,7 +149,7 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
// FIXME - misnamed output field means we have redundant fields // FIXME - misnamed output field means we have redundant fields
disableDropdownCreate := config.GetDisableDropdownCreate() disableDropdownCreate := config.GetDisableDropdownCreate()
return &models.ConfigInterfaceResult{ return &ConfigInterfaceResult{
MenuItems: menuItems, MenuItems: menuItems,
SoundOnPreview: &soundOnPreview, SoundOnPreview: &soundOnPreview,
WallShowTitle: &wallShowTitle, WallShowTitle: &wallShowTitle,
@ -177,10 +177,10 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
} }
} }
func makeConfigDLNAResult() *models.ConfigDLNAResult { func makeConfigDLNAResult() *ConfigDLNAResult {
config := config.GetInstance() config := config.GetInstance()
return &models.ConfigDLNAResult{ return &ConfigDLNAResult{
ServerName: config.GetDLNAServerName(), ServerName: config.GetDLNAServerName(),
Enabled: config.GetDLNADefaultEnabled(), Enabled: config.GetDLNADefaultEnabled(),
WhitelistedIPs: config.GetDLNADefaultIPWhitelist(), WhitelistedIPs: config.GetDLNADefaultIPWhitelist(),
@ -188,13 +188,13 @@ func makeConfigDLNAResult() *models.ConfigDLNAResult {
} }
} }
func makeConfigScrapingResult() *models.ConfigScrapingResult { func makeConfigScrapingResult() *ConfigScrapingResult {
config := config.GetInstance() config := config.GetInstance()
scraperUserAgent := config.GetScraperUserAgent() scraperUserAgent := config.GetScraperUserAgent()
scraperCDPPath := config.GetScraperCDPPath() scraperCDPPath := config.GetScraperCDPPath()
return &models.ConfigScrapingResult{ return &ConfigScrapingResult{
ScraperUserAgent: &scraperUserAgent, ScraperUserAgent: &scraperUserAgent,
ScraperCertCheck: config.GetScraperCertCheck(), ScraperCertCheck: config.GetScraperCertCheck(),
ScraperCDPPath: &scraperCDPPath, ScraperCDPPath: &scraperCDPPath,
@ -202,12 +202,12 @@ func makeConfigScrapingResult() *models.ConfigScrapingResult {
} }
} }
func makeConfigDefaultsResult() *models.ConfigDefaultSettingsResult { func makeConfigDefaultsResult() *ConfigDefaultSettingsResult {
config := config.GetInstance() config := config.GetInstance()
deleteFileDefault := config.GetDeleteFileDefault() deleteFileDefault := config.GetDeleteFileDefault()
deleteGeneratedDefault := config.GetDeleteGeneratedDefault() deleteGeneratedDefault := config.GetDeleteGeneratedDefault()
return &models.ConfigDefaultSettingsResult{ return &ConfigDefaultSettingsResult{
Identify: config.GetDefaultIdentifySettings(), Identify: config.GetDefaultIdentifySettings(),
Scan: config.GetDefaultScanSettings(), Scan: config.GetDefaultScanSettings(),
AutoTag: config.GetDefaultAutoTagSettings(), AutoTag: config.GetDefaultAutoTagSettings(),
@ -221,7 +221,7 @@ func makeConfigUIResult() map[string]interface{} {
return config.GetInstance().GetUIConfiguration() return config.GetInstance().GetUIConfiguration()
} }
func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input models.StashBoxInput) (*models.StashBoxValidationResult, error) { func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input config.StashBoxInput) (*StashBoxValidationResult, error) {
client := stashbox.NewClient(models.StashBox{Endpoint: input.Endpoint, APIKey: input.APIKey}, r.txnManager) client := stashbox.NewClient(models.StashBox{Endpoint: input.Endpoint, APIKey: input.APIKey}, r.txnManager)
user, err := client.GetUser(ctx) user, err := client.GetUser(ctx)
@ -248,7 +248,7 @@ func (r *queryResolver) ValidateStashBoxCredentials(ctx context.Context, input m
} }
} }
result := models.StashBoxValidationResult{ result := StashBoxValidationResult{
Valid: valid, Valid: valid,
Status: status, Status: status,
} }

View file

@ -3,10 +3,10 @@ package api
import ( import (
"context" "context"
"github.com/stashapp/stash/internal/dlna"
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
) )
func (r *queryResolver) DlnaStatus(ctx context.Context) (*models.DLNAStatus, error) { func (r *queryResolver) DlnaStatus(ctx context.Context) (*dlna.Status, error) {
return manager.GetInstance().DLNAService.Status(), nil return manager.GetInstance().DLNAService.Status(), nil
} }

View file

@ -23,14 +23,14 @@ func (r *queryResolver) FindGallery(ctx context.Context, id string) (ret *models
return ret, nil return ret, nil
} }
func (r *queryResolver) FindGalleries(ctx context.Context, galleryFilter *models.GalleryFilterType, filter *models.FindFilterType) (ret *models.FindGalleriesResultType, err error) { func (r *queryResolver) FindGalleries(ctx context.Context, galleryFilter *models.GalleryFilterType, filter *models.FindFilterType) (ret *FindGalleriesResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
galleries, total, err := repo.Gallery().Query(galleryFilter, filter) galleries, total, err := repo.Gallery().Query(galleryFilter, filter)
if err != nil { if err != nil {
return err return err
} }
ret = &models.FindGalleriesResultType{ ret = &FindGalleriesResultType{
Count: total, Count: total,
Galleries: galleries, Galleries: galleries,
} }

View file

@ -38,7 +38,7 @@ func (r *queryResolver) FindImage(ctx context.Context, id *string, checksum *str
return image, nil return image, nil
} }
func (r *queryResolver) FindImages(ctx context.Context, imageFilter *models.ImageFilterType, imageIds []int, filter *models.FindFilterType) (ret *models.FindImagesResultType, err error) { func (r *queryResolver) FindImages(ctx context.Context, imageFilter *models.ImageFilterType, imageIds []int, filter *models.FindFilterType) (ret *FindImagesResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
qb := repo.Image() qb := repo.Image()
@ -62,7 +62,7 @@ func (r *queryResolver) FindImages(ctx context.Context, imageFilter *models.Imag
return err return err
} }
ret = &models.FindImagesResultType{ ret = &FindImagesResultType{
Count: result.Count, Count: result.Count,
Images: images, Images: images,
Megapixels: result.Megapixels, Megapixels: result.Megapixels,

View file

@ -23,14 +23,14 @@ func (r *queryResolver) FindMovie(ctx context.Context, id string) (ret *models.M
return ret, nil return ret, nil
} }
func (r *queryResolver) FindMovies(ctx context.Context, movieFilter *models.MovieFilterType, filter *models.FindFilterType) (ret *models.FindMoviesResultType, err error) { func (r *queryResolver) FindMovies(ctx context.Context, movieFilter *models.MovieFilterType, filter *models.FindFilterType) (ret *FindMoviesResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
movies, total, err := repo.Movie().Query(movieFilter, filter) movies, total, err := repo.Movie().Query(movieFilter, filter)
if err != nil { if err != nil {
return err return err
} }
ret = &models.FindMoviesResultType{ ret = &FindMoviesResultType{
Count: total, Count: total,
Movies: movies, Movies: movies,
} }

View file

@ -23,14 +23,14 @@ func (r *queryResolver) FindPerformer(ctx context.Context, id string) (ret *mode
return ret, nil return ret, nil
} }
func (r *queryResolver) FindPerformers(ctx context.Context, performerFilter *models.PerformerFilterType, filter *models.FindFilterType) (ret *models.FindPerformersResultType, err error) { func (r *queryResolver) FindPerformers(ctx context.Context, performerFilter *models.PerformerFilterType, filter *models.FindFilterType) (ret *FindPerformersResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
performers, total, err := repo.Performer().Query(performerFilter, filter) performers, total, err := repo.Performer().Query(performerFilter, filter)
if err != nil { if err != nil {
return err return err
} }
ret = &models.FindPerformersResultType{ ret = &FindPerformersResultType{
Count: total, Count: total,
Performers: performers, Performers: performers,
} }

View file

@ -36,7 +36,7 @@ func (r *queryResolver) FindScene(ctx context.Context, id *string, checksum *str
return scene, nil return scene, nil
} }
func (r *queryResolver) FindSceneByHash(ctx context.Context, input models.SceneHashInput) (*models.Scene, error) { func (r *queryResolver) FindSceneByHash(ctx context.Context, input SceneHashInput) (*models.Scene, error) {
var scene *models.Scene var scene *models.Scene
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
@ -64,7 +64,7 @@ func (r *queryResolver) FindSceneByHash(ctx context.Context, input models.SceneH
return scene, nil return scene, nil
} }
func (r *queryResolver) FindScenes(ctx context.Context, sceneFilter *models.SceneFilterType, sceneIDs []int, filter *models.FindFilterType) (ret *models.FindScenesResultType, err error) { func (r *queryResolver) FindScenes(ctx context.Context, sceneFilter *models.SceneFilterType, sceneIDs []int, filter *models.FindFilterType) (ret *FindScenesResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
var scenes []*models.Scene var scenes []*models.Scene
var err error var err error
@ -101,7 +101,7 @@ func (r *queryResolver) FindScenes(ctx context.Context, sceneFilter *models.Scen
return err return err
} }
ret = &models.FindScenesResultType{ ret = &FindScenesResultType{
Count: result.Count, Count: result.Count,
Scenes: scenes, Scenes: scenes,
Duration: result.TotalDuration, Duration: result.TotalDuration,
@ -116,7 +116,7 @@ func (r *queryResolver) FindScenes(ctx context.Context, sceneFilter *models.Scen
return ret, nil return ret, nil
} }
func (r *queryResolver) FindScenesByPathRegex(ctx context.Context, filter *models.FindFilterType) (ret *models.FindScenesResultType, err error) { func (r *queryResolver) FindScenesByPathRegex(ctx context.Context, filter *models.FindFilterType) (ret *FindScenesResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
sceneFilter := &models.SceneFilterType{} sceneFilter := &models.SceneFilterType{}
@ -156,7 +156,7 @@ func (r *queryResolver) FindScenesByPathRegex(ctx context.Context, filter *model
return err return err
} }
ret = &models.FindScenesResultType{ ret = &FindScenesResultType{
Count: result.Count, Count: result.Count,
Scenes: scenes, Scenes: scenes,
Duration: result.TotalDuration, Duration: result.TotalDuration,
@ -171,7 +171,7 @@ func (r *queryResolver) FindScenesByPathRegex(ctx context.Context, filter *model
return ret, nil return ret, nil
} }
func (r *queryResolver) ParseSceneFilenames(ctx context.Context, filter *models.FindFilterType, config models.SceneParserInput) (ret *models.SceneParserResultType, err error) { func (r *queryResolver) ParseSceneFilenames(ctx context.Context, filter *models.FindFilterType, config manager.SceneParserInput) (ret *SceneParserResultType, err error) {
parser := manager.NewSceneFilenameParser(filter, config) parser := manager.NewSceneFilenameParser(filter, config)
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
@ -181,7 +181,7 @@ func (r *queryResolver) ParseSceneFilenames(ctx context.Context, filter *models.
return err return err
} }
ret = &models.SceneParserResultType{ ret = &SceneParserResultType{
Count: count, Count: count,
Results: result, Results: result,
} }

View file

@ -6,13 +6,13 @@ import (
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
) )
func (r *queryResolver) FindSceneMarkers(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, filter *models.FindFilterType) (ret *models.FindSceneMarkersResultType, err error) { func (r *queryResolver) FindSceneMarkers(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, filter *models.FindFilterType) (ret *FindSceneMarkersResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
sceneMarkers, total, err := repo.SceneMarker().Query(sceneMarkerFilter, filter) sceneMarkers, total, err := repo.SceneMarker().Query(sceneMarkerFilter, filter)
if err != nil { if err != nil {
return err return err
} }
ret = &models.FindSceneMarkersResultType{ ret = &FindSceneMarkersResultType{
Count: total, Count: total,
SceneMarkers: sceneMarkers, SceneMarkers: sceneMarkers,
} }

View file

@ -24,14 +24,14 @@ func (r *queryResolver) FindStudio(ctx context.Context, id string) (ret *models.
return ret, nil return ret, nil
} }
func (r *queryResolver) FindStudios(ctx context.Context, studioFilter *models.StudioFilterType, filter *models.FindFilterType) (ret *models.FindStudiosResultType, err error) { func (r *queryResolver) FindStudios(ctx context.Context, studioFilter *models.StudioFilterType, filter *models.FindFilterType) (ret *FindStudiosResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
studios, total, err := repo.Studio().Query(studioFilter, filter) studios, total, err := repo.Studio().Query(studioFilter, filter)
if err != nil { if err != nil {
return err return err
} }
ret = &models.FindStudiosResultType{ ret = &FindStudiosResultType{
Count: total, Count: total,
Studios: studios, Studios: studios,
} }

View file

@ -23,14 +23,14 @@ func (r *queryResolver) FindTag(ctx context.Context, id string) (ret *models.Tag
return ret, nil return ret, nil
} }
func (r *queryResolver) FindTags(ctx context.Context, tagFilter *models.TagFilterType, filter *models.FindFilterType) (ret *models.FindTagsResultType, err error) { func (r *queryResolver) FindTags(ctx context.Context, tagFilter *models.TagFilterType, filter *models.FindFilterType) (ret *FindTagsResultType, err error) {
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {
tags, total, err := repo.Tag().Query(tagFilter, filter) tags, total, err := repo.Tag().Query(tagFilter, filter)
if err != nil { if err != nil {
return err return err
} }
ret = &models.FindTagsResultType{ ret = &FindTagsResultType{
Count: total, Count: total,
Tags: tags, Tags: tags,
} }

View file

@ -6,13 +6,12 @@ import (
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/job" "github.com/stashapp/stash/pkg/job"
"github.com/stashapp/stash/pkg/models"
) )
func (r *queryResolver) JobQueue(ctx context.Context) ([]*models.Job, error) { func (r *queryResolver) JobQueue(ctx context.Context) ([]*Job, error) {
queue := manager.GetInstance().JobManager.GetQueue() queue := manager.GetInstance().JobManager.GetQueue()
var ret []*models.Job var ret []*Job
for _, j := range queue { for _, j := range queue {
ret = append(ret, jobToJobModel(j)) ret = append(ret, jobToJobModel(j))
} }
@ -20,7 +19,7 @@ func (r *queryResolver) JobQueue(ctx context.Context) ([]*models.Job, error) {
return ret, nil return ret, nil
} }
func (r *queryResolver) FindJob(ctx context.Context, input models.FindJobInput) (*models.Job, error) { func (r *queryResolver) FindJob(ctx context.Context, input FindJobInput) (*Job, error) {
jobID, err := strconv.Atoi(input.ID) jobID, err := strconv.Atoi(input.ID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -33,10 +32,10 @@ func (r *queryResolver) FindJob(ctx context.Context, input models.FindJobInput)
return jobToJobModel(*j), nil return jobToJobModel(*j), nil
} }
func jobToJobModel(j job.Job) *models.Job { func jobToJobModel(j job.Job) *Job {
ret := &models.Job{ ret := &Job{
ID: strconv.Itoa(j.ID), ID: strconv.Itoa(j.ID),
Status: models.JobStatus(j.Status), Status: JobStatus(j.Status),
Description: j.Description, Description: j.Description,
SubTasks: j.Details, SubTasks: j.Details,
StartTime: j.StartTime, StartTime: j.StartTime,

View file

@ -4,16 +4,15 @@ import (
"context" "context"
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
) )
func (r *queryResolver) Logs(ctx context.Context) ([]*models.LogEntry, error) { func (r *queryResolver) Logs(ctx context.Context) ([]*LogEntry, error) {
logger := manager.GetInstance().Logger logger := manager.GetInstance().Logger
logCache := logger.GetLogCache() logCache := logger.GetLogCache()
ret := make([]*models.LogEntry, len(logCache)) ret := make([]*LogEntry, len(logCache))
for i, entry := range logCache { for i, entry := range logCache {
ret[i] = &models.LogEntry{ ret[i] = &LogEntry{
Time: entry.Time, Time: entry.Time,
Level: getLogLevel(entry.Type), Level: getLogLevel(entry.Type),
Message: entry.Message, Message: entry.Message,

View file

@ -4,9 +4,8 @@ import (
"context" "context"
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
) )
func (r *queryResolver) SystemStatus(ctx context.Context) (*models.SystemStatus, error) { func (r *queryResolver) SystemStatus(ctx context.Context) (*manager.SystemStatus, error) {
return manager.GetInstance().GetSystemStatus(), nil return manager.GetInstance().GetSystemStatus(), nil
} }

View file

@ -4,13 +4,13 @@ import (
"context" "context"
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/plugin"
) )
func (r *queryResolver) Plugins(ctx context.Context) ([]*models.Plugin, error) { func (r *queryResolver) Plugins(ctx context.Context) ([]*plugin.Plugin, error) {
return manager.GetInstance().PluginCache.ListPlugins(), nil return manager.GetInstance().PluginCache.ListPlugins(), nil
} }
func (r *queryResolver) PluginTasks(ctx context.Context) ([]*models.PluginTask, error) { func (r *queryResolver) PluginTasks(ctx context.Context) ([]*plugin.PluginTask, error) {
return manager.GetInstance().PluginCache.ListPluginTasks(), nil return manager.GetInstance().PluginCache.ListPluginTasks(), nil
} }

View file

@ -11,7 +11,7 @@ import (
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
) )
func (r *queryResolver) SceneStreams(ctx context.Context, id *string) ([]*models.SceneStreamEndpoint, error) { func (r *queryResolver) SceneStreams(ctx context.Context, id *string) ([]*manager.SceneStreamEndpoint, error) {
// find the scene // find the scene
var scene *models.Scene var scene *models.Scene
if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error { if err := r.withReadTxn(ctx, func(repo models.ReaderRepository) error {

View file

@ -17,13 +17,13 @@ import (
"github.com/stashapp/stash/pkg/sliceutil/stringslice" "github.com/stashapp/stash/pkg/sliceutil/stringslice"
) )
func (r *queryResolver) ScrapeURL(ctx context.Context, url string, ty models.ScrapeContentType) (models.ScrapedContent, error) { func (r *queryResolver) ScrapeURL(ctx context.Context, url string, ty scraper.ScrapeContentType) (scraper.ScrapedContent, error) {
return r.scraperCache().ScrapeURL(ctx, url, ty) return r.scraperCache().ScrapeURL(ctx, url, ty)
} }
// deprecated // deprecated
func (r *queryResolver) ScrapeFreeonesPerformerList(ctx context.Context, query string) ([]string, error) { func (r *queryResolver) ScrapeFreeonesPerformerList(ctx context.Context, query string) ([]string, error) {
content, err := r.scraperCache().ScrapeName(ctx, scraper.FreeonesScraperID, query, models.ScrapeContentTypePerformer) content, err := r.scraperCache().ScrapeName(ctx, scraper.FreeonesScraperID, query, scraper.ScrapeContentTypePerformer)
if err != nil { if err != nil {
return nil, err return nil, err
@ -44,24 +44,24 @@ func (r *queryResolver) ScrapeFreeonesPerformerList(ctx context.Context, query s
return ret, nil return ret, nil
} }
func (r *queryResolver) ListScrapers(ctx context.Context, types []models.ScrapeContentType) ([]*models.Scraper, error) { func (r *queryResolver) ListScrapers(ctx context.Context, types []scraper.ScrapeContentType) ([]*scraper.Scraper, error) {
return r.scraperCache().ListScrapers(types), nil return r.scraperCache().ListScrapers(types), nil
} }
func (r *queryResolver) ListPerformerScrapers(ctx context.Context) ([]*models.Scraper, error) { func (r *queryResolver) ListPerformerScrapers(ctx context.Context) ([]*scraper.Scraper, error) {
return r.scraperCache().ListScrapers([]models.ScrapeContentType{models.ScrapeContentTypePerformer}), nil return r.scraperCache().ListScrapers([]scraper.ScrapeContentType{scraper.ScrapeContentTypePerformer}), nil
} }
func (r *queryResolver) ListSceneScrapers(ctx context.Context) ([]*models.Scraper, error) { func (r *queryResolver) ListSceneScrapers(ctx context.Context) ([]*scraper.Scraper, error) {
return r.scraperCache().ListScrapers([]models.ScrapeContentType{models.ScrapeContentTypeScene}), nil return r.scraperCache().ListScrapers([]scraper.ScrapeContentType{scraper.ScrapeContentTypeScene}), nil
} }
func (r *queryResolver) ListGalleryScrapers(ctx context.Context) ([]*models.Scraper, error) { func (r *queryResolver) ListGalleryScrapers(ctx context.Context) ([]*scraper.Scraper, error) {
return r.scraperCache().ListScrapers([]models.ScrapeContentType{models.ScrapeContentTypeGallery}), nil return r.scraperCache().ListScrapers([]scraper.ScrapeContentType{scraper.ScrapeContentTypeGallery}), nil
} }
func (r *queryResolver) ListMovieScrapers(ctx context.Context) ([]*models.Scraper, error) { func (r *queryResolver) ListMovieScrapers(ctx context.Context) ([]*scraper.Scraper, error) {
return r.scraperCache().ListScrapers([]models.ScrapeContentType{models.ScrapeContentTypeMovie}), nil return r.scraperCache().ListScrapers([]scraper.ScrapeContentType{scraper.ScrapeContentTypeMovie}), nil
} }
func (r *queryResolver) ScrapePerformerList(ctx context.Context, scraperID string, query string) ([]*models.ScrapedPerformer, error) { func (r *queryResolver) ScrapePerformerList(ctx context.Context, scraperID string, query string) ([]*models.ScrapedPerformer, error) {
@ -69,7 +69,7 @@ func (r *queryResolver) ScrapePerformerList(ctx context.Context, scraperID strin
return nil, nil return nil, nil
} }
content, err := r.scraperCache().ScrapeName(ctx, scraperID, query, models.ScrapeContentTypePerformer) content, err := r.scraperCache().ScrapeName(ctx, scraperID, query, scraper.ScrapeContentTypePerformer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -77,7 +77,7 @@ func (r *queryResolver) ScrapePerformerList(ctx context.Context, scraperID strin
return marshalScrapedPerformers(content) return marshalScrapedPerformers(content)
} }
func (r *queryResolver) ScrapePerformer(ctx context.Context, scraperID string, scrapedPerformer models.ScrapedPerformerInput) (*models.ScrapedPerformer, error) { func (r *queryResolver) ScrapePerformer(ctx context.Context, scraperID string, scrapedPerformer scraper.ScrapedPerformerInput) (*models.ScrapedPerformer, error) {
content, err := r.scraperCache().ScrapeFragment(ctx, scraperID, scraper.Input{Performer: &scrapedPerformer}) content, err := r.scraperCache().ScrapeFragment(ctx, scraperID, scraper.Input{Performer: &scrapedPerformer})
if err != nil { if err != nil {
return nil, err return nil, err
@ -86,7 +86,7 @@ func (r *queryResolver) ScrapePerformer(ctx context.Context, scraperID string, s
} }
func (r *queryResolver) ScrapePerformerURL(ctx context.Context, url string) (*models.ScrapedPerformer, error) { func (r *queryResolver) ScrapePerformerURL(ctx context.Context, url string) (*models.ScrapedPerformer, error) {
content, err := r.scraperCache().ScrapeURL(ctx, url, models.ScrapeContentTypePerformer) content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypePerformer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -94,12 +94,12 @@ func (r *queryResolver) ScrapePerformerURL(ctx context.Context, url string) (*mo
return marshalScrapedPerformer(content) return marshalScrapedPerformer(content)
} }
func (r *queryResolver) ScrapeSceneQuery(ctx context.Context, scraperID string, query string) ([]*models.ScrapedScene, error) { func (r *queryResolver) ScrapeSceneQuery(ctx context.Context, scraperID string, query string) ([]*scraper.ScrapedScene, error) {
if query == "" { if query == "" {
return nil, nil return nil, nil
} }
content, err := r.scraperCache().ScrapeName(ctx, scraperID, query, models.ScrapeContentTypeScene) content, err := r.scraperCache().ScrapeName(ctx, scraperID, query, scraper.ScrapeContentTypeScene)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -113,13 +113,13 @@ func (r *queryResolver) ScrapeSceneQuery(ctx context.Context, scraperID string,
return ret, nil return ret, nil
} }
func (r *queryResolver) ScrapeScene(ctx context.Context, scraperID string, scene models.SceneUpdateInput) (*models.ScrapedScene, error) { func (r *queryResolver) ScrapeScene(ctx context.Context, scraperID string, scene models.SceneUpdateInput) (*scraper.ScrapedScene, error) {
id, err := strconv.Atoi(scene.ID) id, err := strconv.Atoi(scene.ID)
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: scene.ID is not an integer: '%s'", ErrInput, scene.ID) return nil, fmt.Errorf("%w: scene.ID is not an integer: '%s'", ErrInput, scene.ID)
} }
content, err := r.scraperCache().ScrapeID(ctx, scraperID, id, models.ScrapeContentTypeScene) content, err := r.scraperCache().ScrapeID(ctx, scraperID, id, scraper.ScrapeContentTypeScene)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -129,13 +129,13 @@ func (r *queryResolver) ScrapeScene(ctx context.Context, scraperID string, scene
return nil, err return nil, err
} }
filterSceneTags([]*models.ScrapedScene{ret}) filterSceneTags([]*scraper.ScrapedScene{ret})
return ret, nil return ret, nil
} }
// filterSceneTags removes tags matching excluded tag patterns from the provided scraped scenes // filterSceneTags removes tags matching excluded tag patterns from the provided scraped scenes
func filterSceneTags(scenes []*models.ScrapedScene) { func filterSceneTags(scenes []*scraper.ScrapedScene) {
excludePatterns := manager.GetInstance().Config.GetScraperExcludeTagPatterns() excludePatterns := manager.GetInstance().Config.GetScraperExcludeTagPatterns()
var excludeRegexps []*regexp.Regexp var excludeRegexps []*regexp.Regexp
@ -179,8 +179,8 @@ func filterSceneTags(scenes []*models.ScrapedScene) {
} }
} }
func (r *queryResolver) ScrapeSceneURL(ctx context.Context, url string) (*models.ScrapedScene, error) { func (r *queryResolver) ScrapeSceneURL(ctx context.Context, url string) (*scraper.ScrapedScene, error) {
content, err := r.scraperCache().ScrapeURL(ctx, url, models.ScrapeContentTypeScene) content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeScene)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -190,18 +190,18 @@ func (r *queryResolver) ScrapeSceneURL(ctx context.Context, url string) (*models
return nil, err return nil, err
} }
filterSceneTags([]*models.ScrapedScene{ret}) filterSceneTags([]*scraper.ScrapedScene{ret})
return ret, nil return ret, nil
} }
func (r *queryResolver) ScrapeGallery(ctx context.Context, scraperID string, gallery models.GalleryUpdateInput) (*models.ScrapedGallery, error) { func (r *queryResolver) ScrapeGallery(ctx context.Context, scraperID string, gallery models.GalleryUpdateInput) (*scraper.ScrapedGallery, error) {
id, err := strconv.Atoi(gallery.ID) id, err := strconv.Atoi(gallery.ID)
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: gallery id is not an integer: '%s'", ErrInput, gallery.ID) return nil, fmt.Errorf("%w: gallery id is not an integer: '%s'", ErrInput, gallery.ID)
} }
content, err := r.scraperCache().ScrapeID(ctx, scraperID, id, models.ScrapeContentTypeGallery) content, err := r.scraperCache().ScrapeID(ctx, scraperID, id, scraper.ScrapeContentTypeGallery)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -209,8 +209,8 @@ func (r *queryResolver) ScrapeGallery(ctx context.Context, scraperID string, gal
return marshalScrapedGallery(content) return marshalScrapedGallery(content)
} }
func (r *queryResolver) ScrapeGalleryURL(ctx context.Context, url string) (*models.ScrapedGallery, error) { func (r *queryResolver) ScrapeGalleryURL(ctx context.Context, url string) (*scraper.ScrapedGallery, error) {
content, err := r.scraperCache().ScrapeURL(ctx, url, models.ScrapeContentTypeGallery) content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeGallery)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -219,7 +219,7 @@ func (r *queryResolver) ScrapeGalleryURL(ctx context.Context, url string) (*mode
} }
func (r *queryResolver) ScrapeMovieURL(ctx context.Context, url string) (*models.ScrapedMovie, error) { func (r *queryResolver) ScrapeMovieURL(ctx context.Context, url string) (*models.ScrapedMovie, error) {
content, err := r.scraperCache().ScrapeURL(ctx, url, models.ScrapeContentTypeMovie) content, err := r.scraperCache().ScrapeURL(ctx, url, scraper.ScrapeContentTypeMovie)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -237,8 +237,8 @@ func (r *queryResolver) getStashBoxClient(index int) (*stashbox.Client, error) {
return stashbox.NewClient(*boxes[index], r.txnManager), nil return stashbox.NewClient(*boxes[index], r.txnManager), nil
} }
func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeSingleSceneInput) ([]*models.ScrapedScene, error) { func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source scraper.Source, input ScrapeSingleSceneInput) ([]*scraper.ScrapedScene, error) {
var ret []*models.ScrapedScene var ret []*scraper.ScrapedScene
var sceneID int var sceneID int
if input.SceneID != nil { if input.SceneID != nil {
@ -252,22 +252,22 @@ func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source models.Scr
switch { switch {
case source.ScraperID != nil: case source.ScraperID != nil:
var err error var err error
var c models.ScrapedContent var c scraper.ScrapedContent
var content []models.ScrapedContent var content []scraper.ScrapedContent
switch { switch {
case input.SceneID != nil: case input.SceneID != nil:
c, err = r.scraperCache().ScrapeID(ctx, *source.ScraperID, sceneID, models.ScrapeContentTypeScene) c, err = r.scraperCache().ScrapeID(ctx, *source.ScraperID, sceneID, scraper.ScrapeContentTypeScene)
if c != nil { if c != nil {
content = []models.ScrapedContent{c} content = []scraper.ScrapedContent{c}
} }
case input.SceneInput != nil: case input.SceneInput != nil:
c, err = r.scraperCache().ScrapeFragment(ctx, *source.ScraperID, scraper.Input{Scene: input.SceneInput}) c, err = r.scraperCache().ScrapeFragment(ctx, *source.ScraperID, scraper.Input{Scene: input.SceneInput})
if c != nil { if c != nil {
content = []models.ScrapedContent{c} content = []scraper.ScrapedContent{c}
} }
case input.Query != nil: case input.Query != nil:
content, err = r.scraperCache().ScrapeName(ctx, *source.ScraperID, *input.Query, models.ScrapeContentTypeScene) content, err = r.scraperCache().ScrapeName(ctx, *source.ScraperID, *input.Query, scraper.ScrapeContentTypeScene)
default: default:
err = fmt.Errorf("%w: scene_id, scene_input, or query must be set", ErrInput) err = fmt.Errorf("%w: scene_id, scene_input, or query must be set", ErrInput)
} }
@ -307,7 +307,7 @@ func (r *queryResolver) ScrapeSingleScene(ctx context.Context, source models.Scr
return ret, nil return ret, nil
} }
func (r *queryResolver) ScrapeMultiScenes(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeMultiScenesInput) ([][]*models.ScrapedScene, error) { func (r *queryResolver) ScrapeMultiScenes(ctx context.Context, source scraper.Source, input ScrapeMultiScenesInput) ([][]*scraper.ScrapedScene, error) {
if source.ScraperID != nil { if source.ScraperID != nil {
return nil, ErrNotImplemented return nil, ErrNotImplemented
} else if source.StashBoxIndex != nil { } else if source.StashBoxIndex != nil {
@ -327,7 +327,7 @@ func (r *queryResolver) ScrapeMultiScenes(ctx context.Context, source models.Scr
return nil, errors.New("scraper_id or stash_box_index must be set") return nil, errors.New("scraper_id or stash_box_index must be set")
} }
func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeSinglePerformerInput) ([]*models.ScrapedPerformer, error) { func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source scraper.Source, input ScrapeSinglePerformerInput) ([]*models.ScrapedPerformer, error) {
if source.ScraperID != nil { if source.ScraperID != nil {
if input.PerformerInput != nil { if input.PerformerInput != nil {
performer, err := r.scraperCache().ScrapeFragment(ctx, *source.ScraperID, scraper.Input{Performer: input.PerformerInput}) performer, err := r.scraperCache().ScrapeFragment(ctx, *source.ScraperID, scraper.Input{Performer: input.PerformerInput})
@ -335,11 +335,11 @@ func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source models
return nil, err return nil, err
} }
return marshalScrapedPerformers([]models.ScrapedContent{performer}) return marshalScrapedPerformers([]scraper.ScrapedContent{performer})
} }
if input.Query != nil { if input.Query != nil {
content, err := r.scraperCache().ScrapeName(ctx, *source.ScraperID, *input.Query, models.ScrapeContentTypePerformer) content, err := r.scraperCache().ScrapeName(ctx, *source.ScraperID, *input.Query, scraper.ScrapeContentTypePerformer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -354,7 +354,7 @@ func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source models
return nil, err return nil, err
} }
var ret []*models.StashBoxPerformerQueryResult var ret []*stashbox.StashBoxPerformerQueryResult
switch { switch {
case input.PerformerID != nil: case input.PerformerID != nil:
ret, err = client.FindStashBoxPerformersByNames(ctx, []string{*input.PerformerID}) ret, err = client.FindStashBoxPerformersByNames(ctx, []string{*input.PerformerID})
@ -378,7 +378,7 @@ func (r *queryResolver) ScrapeSinglePerformer(ctx context.Context, source models
return nil, errors.New("scraper_id or stash_box_index must be set") return nil, errors.New("scraper_id or stash_box_index must be set")
} }
func (r *queryResolver) ScrapeMultiPerformers(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeMultiPerformersInput) ([][]*models.ScrapedPerformer, error) { func (r *queryResolver) ScrapeMultiPerformers(ctx context.Context, source scraper.Source, input ScrapeMultiPerformersInput) ([][]*models.ScrapedPerformer, error) {
if source.ScraperID != nil { if source.ScraperID != nil {
return nil, ErrNotImplemented return nil, ErrNotImplemented
} else if source.StashBoxIndex != nil { } else if source.StashBoxIndex != nil {
@ -393,7 +393,7 @@ func (r *queryResolver) ScrapeMultiPerformers(ctx context.Context, source models
return nil, errors.New("scraper_id or stash_box_index must be set") return nil, errors.New("scraper_id or stash_box_index must be set")
} }
func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeSingleGalleryInput) ([]*models.ScrapedGallery, error) { func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source scraper.Source, input ScrapeSingleGalleryInput) ([]*scraper.ScrapedGallery, error) {
if source.StashBoxIndex != nil { if source.StashBoxIndex != nil {
return nil, ErrNotSupported return nil, ErrNotSupported
} }
@ -402,7 +402,7 @@ func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source models.S
return nil, fmt.Errorf("%w: scraper_id must be set", ErrInput) return nil, fmt.Errorf("%w: scraper_id must be set", ErrInput)
} }
var c models.ScrapedContent var c scraper.ScrapedContent
switch { switch {
case input.GalleryID != nil: case input.GalleryID != nil:
@ -410,22 +410,22 @@ func (r *queryResolver) ScrapeSingleGallery(ctx context.Context, source models.S
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: gallery id is not an integer: '%s'", ErrInput, *input.GalleryID) return nil, fmt.Errorf("%w: gallery id is not an integer: '%s'", ErrInput, *input.GalleryID)
} }
c, err = r.scraperCache().ScrapeID(ctx, *source.ScraperID, galleryID, models.ScrapeContentTypeGallery) c, err = r.scraperCache().ScrapeID(ctx, *source.ScraperID, galleryID, scraper.ScrapeContentTypeGallery)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return marshalScrapedGalleries([]models.ScrapedContent{c}) return marshalScrapedGalleries([]scraper.ScrapedContent{c})
case input.GalleryInput != nil: case input.GalleryInput != nil:
c, err := r.scraperCache().ScrapeFragment(ctx, *source.ScraperID, scraper.Input{Gallery: input.GalleryInput}) c, err := r.scraperCache().ScrapeFragment(ctx, *source.ScraperID, scraper.Input{Gallery: input.GalleryInput})
if err != nil { if err != nil {
return nil, err return nil, err
} }
return marshalScrapedGalleries([]models.ScrapedContent{c}) return marshalScrapedGalleries([]scraper.ScrapedContent{c})
default: default:
return nil, ErrNotImplemented return nil, ErrNotImplemented
} }
} }
func (r *queryResolver) ScrapeSingleMovie(ctx context.Context, source models.ScraperSourceInput, input models.ScrapeSingleMovieInput) ([]*models.ScrapedMovie, error) { func (r *queryResolver) ScrapeSingleMovie(ctx context.Context, source scraper.Source, input ScrapeSingleMovieInput) ([]*models.ScrapedMovie, error) {
return nil, ErrNotSupported return nil, ErrNotSupported
} }

View file

@ -5,18 +5,17 @@ import (
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/job" "github.com/stashapp/stash/pkg/job"
"github.com/stashapp/stash/pkg/models"
) )
func makeJobStatusUpdate(t models.JobStatusUpdateType, j job.Job) *models.JobStatusUpdate { func makeJobStatusUpdate(t JobStatusUpdateType, j job.Job) *JobStatusUpdate {
return &models.JobStatusUpdate{ return &JobStatusUpdate{
Type: t, Type: t,
Job: jobToJobModel(j), Job: jobToJobModel(j),
} }
} }
func (r *subscriptionResolver) JobsSubscribe(ctx context.Context) (<-chan *models.JobStatusUpdate, error) { func (r *subscriptionResolver) JobsSubscribe(ctx context.Context) (<-chan *JobStatusUpdate, error) {
msg := make(chan *models.JobStatusUpdate, 100) msg := make(chan *JobStatusUpdate, 100)
subscription := manager.GetInstance().JobManager.Subscribe(ctx) subscription := manager.GetInstance().JobManager.Subscribe(ctx)
@ -24,11 +23,11 @@ func (r *subscriptionResolver) JobsSubscribe(ctx context.Context) (<-chan *model
for { for {
select { select {
case j := <-subscription.NewJob: case j := <-subscription.NewJob:
msg <- makeJobStatusUpdate(models.JobStatusUpdateTypeAdd, j) msg <- makeJobStatusUpdate(JobStatusUpdateTypeAdd, j)
case j := <-subscription.RemovedJob: case j := <-subscription.RemovedJob:
msg <- makeJobStatusUpdate(models.JobStatusUpdateTypeRemove, j) msg <- makeJobStatusUpdate(JobStatusUpdateTypeRemove, j)
case j := <-subscription.UpdatedJob: case j := <-subscription.UpdatedJob:
msg <- makeJobStatusUpdate(models.JobStatusUpdateTypeUpdate, j) msg <- makeJobStatusUpdate(JobStatusUpdateTypeUpdate, j)
case <-ctx.Done(): case <-ctx.Done():
close(msg) close(msg)
return return

View file

@ -5,33 +5,32 @@ import (
"github.com/stashapp/stash/internal/log" "github.com/stashapp/stash/internal/log"
"github.com/stashapp/stash/internal/manager" "github.com/stashapp/stash/internal/manager"
"github.com/stashapp/stash/pkg/models"
) )
func getLogLevel(logType string) models.LogLevel { func getLogLevel(logType string) LogLevel {
switch logType { switch logType {
case "progress": case "progress":
return models.LogLevelProgress return LogLevelProgress
case "trace": case "trace":
return models.LogLevelTrace return LogLevelTrace
case "debug": case "debug":
return models.LogLevelDebug return LogLevelDebug
case "info": case "info":
return models.LogLevelInfo return LogLevelInfo
case "warn": case "warn":
return models.LogLevelWarning return LogLevelWarning
case "error": case "error":
return models.LogLevelError return LogLevelError
default: default:
return models.LogLevelDebug return LogLevelDebug
} }
} }
func logEntriesFromLogItems(logItems []log.LogItem) []*models.LogEntry { func logEntriesFromLogItems(logItems []log.LogItem) []*LogEntry {
ret := make([]*models.LogEntry, len(logItems)) ret := make([]*LogEntry, len(logItems))
for i, entry := range logItems { for i, entry := range logItems {
ret[i] = &models.LogEntry{ ret[i] = &LogEntry{
Time: entry.Time, Time: entry.Time,
Level: getLogLevel(entry.Type), Level: getLogLevel(entry.Type),
Message: entry.Message, Message: entry.Message,
@ -41,8 +40,8 @@ func logEntriesFromLogItems(logItems []log.LogItem) []*models.LogEntry {
return ret return ret
} }
func (r *subscriptionResolver) LoggingSubscribe(ctx context.Context) (<-chan []*models.LogEntry, error) { func (r *subscriptionResolver) LoggingSubscribe(ctx context.Context) (<-chan []*LogEntry, error) {
ret := make(chan []*models.LogEntry, 100) ret := make(chan []*LogEntry, 100)
stop := make(chan int, 1) stop := make(chan int, 1)
logger := manager.GetInstance().Logger logger := manager.GetInstance().Logger
logSub := logger.SubscribeToLog(stop) logSub := logger.SubscribeToLog(stop)

View file

@ -4,12 +4,13 @@ import (
"fmt" "fmt"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scraper"
) )
// marshalScrapedScenes converts ScrapedContent into ScrapedScene. If conversion fails, an // marshalScrapedScenes converts ScrapedContent into ScrapedScene. If conversion fails, an
// error is returned to the caller. // error is returned to the caller.
func marshalScrapedScenes(content []models.ScrapedContent) ([]*models.ScrapedScene, error) { func marshalScrapedScenes(content []scraper.ScrapedContent) ([]*scraper.ScrapedScene, error) {
var ret []*models.ScrapedScene var ret []*scraper.ScrapedScene
for _, c := range content { for _, c := range content {
if c == nil { if c == nil {
// graphql schema requires scenes to be non-nil // graphql schema requires scenes to be non-nil
@ -17,9 +18,9 @@ func marshalScrapedScenes(content []models.ScrapedContent) ([]*models.ScrapedSce
} }
switch s := c.(type) { switch s := c.(type) {
case *models.ScrapedScene: case *scraper.ScrapedScene:
ret = append(ret, s) ret = append(ret, s)
case models.ScrapedScene: case scraper.ScrapedScene:
ret = append(ret, &s) ret = append(ret, &s)
default: default:
return nil, fmt.Errorf("%w: cannot turn ScrapedContent into ScrapedScene", models.ErrConversion) return nil, fmt.Errorf("%w: cannot turn ScrapedContent into ScrapedScene", models.ErrConversion)
@ -31,7 +32,7 @@ func marshalScrapedScenes(content []models.ScrapedContent) ([]*models.ScrapedSce
// marshalScrapedPerformers converts ScrapedContent into ScrapedPerformer. If conversion // marshalScrapedPerformers converts ScrapedContent into ScrapedPerformer. If conversion
// fails, an error is returned to the caller. // fails, an error is returned to the caller.
func marshalScrapedPerformers(content []models.ScrapedContent) ([]*models.ScrapedPerformer, error) { func marshalScrapedPerformers(content []scraper.ScrapedContent) ([]*models.ScrapedPerformer, error) {
var ret []*models.ScrapedPerformer var ret []*models.ScrapedPerformer
for _, c := range content { for _, c := range content {
if c == nil { if c == nil {
@ -54,8 +55,8 @@ func marshalScrapedPerformers(content []models.ScrapedContent) ([]*models.Scrape
// marshalScrapedGalleries converts ScrapedContent into ScrapedGallery. If // marshalScrapedGalleries converts ScrapedContent into ScrapedGallery. If
// conversion fails, an error is returned. // conversion fails, an error is returned.
func marshalScrapedGalleries(content []models.ScrapedContent) ([]*models.ScrapedGallery, error) { func marshalScrapedGalleries(content []scraper.ScrapedContent) ([]*scraper.ScrapedGallery, error) {
var ret []*models.ScrapedGallery var ret []*scraper.ScrapedGallery
for _, c := range content { for _, c := range content {
if c == nil { if c == nil {
// graphql schema requires galleries to be non-nil // graphql schema requires galleries to be non-nil
@ -63,9 +64,9 @@ func marshalScrapedGalleries(content []models.ScrapedContent) ([]*models.Scraped
} }
switch g := c.(type) { switch g := c.(type) {
case *models.ScrapedGallery: case *scraper.ScrapedGallery:
ret = append(ret, g) ret = append(ret, g)
case models.ScrapedGallery: case scraper.ScrapedGallery:
ret = append(ret, &g) ret = append(ret, &g)
default: default:
return nil, fmt.Errorf("%w: cannot turn ScrapedContent into ScrapedGallery", models.ErrConversion) return nil, fmt.Errorf("%w: cannot turn ScrapedContent into ScrapedGallery", models.ErrConversion)
@ -77,7 +78,7 @@ func marshalScrapedGalleries(content []models.ScrapedContent) ([]*models.Scraped
// marshalScrapedMovies converts ScrapedContent into ScrapedMovie. If conversion // marshalScrapedMovies converts ScrapedContent into ScrapedMovie. If conversion
// fails, an error is returned. // fails, an error is returned.
func marshalScrapedMovies(content []models.ScrapedContent) ([]*models.ScrapedMovie, error) { func marshalScrapedMovies(content []scraper.ScrapedContent) ([]*models.ScrapedMovie, error) {
var ret []*models.ScrapedMovie var ret []*models.ScrapedMovie
for _, c := range content { for _, c := range content {
if c == nil { if c == nil {
@ -99,8 +100,8 @@ func marshalScrapedMovies(content []models.ScrapedContent) ([]*models.ScrapedMov
} }
// marshalScrapedPerformer will marshal a single performer // marshalScrapedPerformer will marshal a single performer
func marshalScrapedPerformer(content models.ScrapedContent) (*models.ScrapedPerformer, error) { func marshalScrapedPerformer(content scraper.ScrapedContent) (*models.ScrapedPerformer, error) {
p, err := marshalScrapedPerformers([]models.ScrapedContent{content}) p, err := marshalScrapedPerformers([]scraper.ScrapedContent{content})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -109,8 +110,8 @@ func marshalScrapedPerformer(content models.ScrapedContent) (*models.ScrapedPerf
} }
// marshalScrapedScene will marshal a single scraped scene // marshalScrapedScene will marshal a single scraped scene
func marshalScrapedScene(content models.ScrapedContent) (*models.ScrapedScene, error) { func marshalScrapedScene(content scraper.ScrapedContent) (*scraper.ScrapedScene, error) {
s, err := marshalScrapedScenes([]models.ScrapedContent{content}) s, err := marshalScrapedScenes([]scraper.ScrapedContent{content})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -119,8 +120,8 @@ func marshalScrapedScene(content models.ScrapedContent) (*models.ScrapedScene, e
} }
// marshalScrapedGallery will marshal a single scraped gallery // marshalScrapedGallery will marshal a single scraped gallery
func marshalScrapedGallery(content models.ScrapedContent) (*models.ScrapedGallery, error) { func marshalScrapedGallery(content scraper.ScrapedContent) (*scraper.ScrapedGallery, error) {
g, err := marshalScrapedGalleries([]models.ScrapedContent{content}) g, err := marshalScrapedGalleries([]scraper.ScrapedContent{content})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -129,8 +130,8 @@ func marshalScrapedGallery(content models.ScrapedContent) (*models.ScrapedGaller
} }
// marshalScrapedMovie will marshal a single scraped movie // marshalScrapedMovie will marshal a single scraped movie
func marshalScrapedMovie(content models.ScrapedContent) (*models.ScrapedMovie, error) { func marshalScrapedMovie(content scraper.ScrapedContent) (*models.ScrapedMovie, error) {
m, err := marshalScrapedMovies([]models.ScrapedContent{content}) m, err := marshalScrapedMovies([]scraper.ScrapedContent{content})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -30,7 +30,6 @@ import (
"github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/ui" "github.com/stashapp/stash/ui"
) )
@ -81,7 +80,7 @@ func Start() error {
hookExecutor: pluginCache, hookExecutor: pluginCache,
} }
gqlSrv := gqlHandler.New(models.NewExecutableSchema(models.Config{Resolvers: resolver})) gqlSrv := gqlHandler.New(NewExecutableSchema(Config{Resolvers: resolver}))
gqlSrv.SetRecoverFunc(recoverFunc) gqlSrv.SetRecoverFunc(recoverFunc)
gqlSrv.AddTransport(gqlTransport.Websocket{ gqlSrv.AddTransport(gqlTransport.Websocket{
Upgrader: websocket.Upgrader{ Upgrader: websocket.Upgrader{

View file

@ -12,6 +12,20 @@ import (
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
) )
type Status struct {
Running bool `json:"running"`
// If not currently running, time until it will be started. If running, time until it will be stopped
Until *time.Time `json:"until"`
RecentIPAddresses []string `json:"recentIPAddresses"`
AllowedIPAddresses []*Dlnaip `json:"allowedIPAddresses"`
}
type Dlnaip struct {
IPAddress string `json:"ipAddress"`
// Time until IP will be no longer allowed/disallowed
Until *time.Time `json:"until"`
}
type dmsConfig struct { type dmsConfig struct {
Path string Path string
IfNames []string IfNames []string
@ -273,11 +287,11 @@ func (s *Service) IsRunning() bool {
return s.running return s.running
} }
func (s *Service) Status() *models.DLNAStatus { func (s *Service) Status() *Status {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
ret := &models.DLNAStatus{ ret := &Status{
Running: s.running, Running: s.running,
RecentIPAddresses: s.ipWhitelistMgr.getRecent(), RecentIPAddresses: s.ipWhitelistMgr.getRecent(),
AllowedIPAddresses: s.ipWhitelistMgr.getTempAllowed(), AllowedIPAddresses: s.ipWhitelistMgr.getTempAllowed(),

View file

@ -4,7 +4,6 @@ import (
"sync" "sync"
"time" "time"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/sliceutil/stringslice" "github.com/stashapp/stash/pkg/sliceutil/stringslice"
) )
@ -59,11 +58,11 @@ func (m *ipWhitelistManager) getRecent() []string {
return m.recentIPAddresses return m.recentIPAddresses
} }
func (m *ipWhitelistManager) getTempAllowed() []*models.Dlnaip { func (m *ipWhitelistManager) getTempAllowed() []*Dlnaip {
m.mutex.Lock() m.mutex.Lock()
defer m.mutex.Unlock() defer m.mutex.Unlock()
var ret []*models.Dlnaip var ret []*Dlnaip
now := time.Now() now := time.Now()
removeExpired := false removeExpired := false
@ -73,7 +72,7 @@ func (m *ipWhitelistManager) getTempAllowed() []*models.Dlnaip {
continue continue
} }
ret = append(ret, &models.Dlnaip{ ret = append(ret, &Dlnaip{
IPAddress: a.pattern, IPAddress: a.pattern,
Until: a.until, Until: a.until,
}) })

View file

@ -8,11 +8,12 @@ import (
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/scene" "github.com/stashapp/stash/pkg/scene"
"github.com/stashapp/stash/pkg/scraper"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
) )
type SceneScraper interface { type SceneScraper interface {
ScrapeScene(ctx context.Context, sceneID int) (*models.ScrapedScene, error) ScrapeScene(ctx context.Context, sceneID int) (*scraper.ScrapedScene, error)
} }
type SceneUpdatePostHookExecutor interface { type SceneUpdatePostHookExecutor interface {
@ -21,13 +22,13 @@ type SceneUpdatePostHookExecutor interface {
type ScraperSource struct { type ScraperSource struct {
Name string Name string
Options *models.IdentifyMetadataOptionsInput Options *MetadataOptions
Scraper SceneScraper Scraper SceneScraper
RemoteSite string RemoteSite string
} }
type SceneIdentifier struct { type SceneIdentifier struct {
DefaultOptions *models.IdentifyMetadataOptionsInput DefaultOptions *MetadataOptions
Sources []ScraperSource Sources []ScraperSource
ScreenshotSetter scene.ScreenshotSetter ScreenshotSetter scene.ScreenshotSetter
SceneUpdatePostHookExecutor SceneUpdatePostHookExecutor SceneUpdatePostHookExecutor SceneUpdatePostHookExecutor
@ -53,7 +54,7 @@ func (t *SceneIdentifier) Identify(ctx context.Context, txnManager models.Transa
} }
type scrapeResult struct { type scrapeResult struct {
result *models.ScrapedScene result *scraper.ScrapedScene
source ScraperSource source ScraperSource
} }
@ -84,7 +85,7 @@ func (t *SceneIdentifier) getSceneUpdater(ctx context.Context, s *models.Scene,
ID: s.ID, ID: s.ID,
} }
options := []models.IdentifyMetadataOptionsInput{} options := []MetadataOptions{}
if result.source.Options != nil { if result.source.Options != nil {
options = append(options, *result.source.Options) options = append(options, *result.source.Options)
} }
@ -208,9 +209,9 @@ func (t *SceneIdentifier) modifyScene(ctx context.Context, txnManager models.Tra
return nil return nil
} }
func getFieldOptions(options []models.IdentifyMetadataOptionsInput) map[string]*models.IdentifyFieldOptionsInput { func getFieldOptions(options []MetadataOptions) map[string]*FieldOptions {
// prefer source-specific field strategies, then the defaults // prefer source-specific field strategies, then the defaults
ret := make(map[string]*models.IdentifyFieldOptionsInput) ret := make(map[string]*FieldOptions)
for _, oo := range options { for _, oo := range options {
for _, f := range oo.FieldOptions { for _, f := range oo.FieldOptions {
if _, found := ret[f.Field]; !found { if _, found := ret[f.Field]; !found {
@ -222,7 +223,7 @@ func getFieldOptions(options []models.IdentifyMetadataOptionsInput) map[string]*
return ret return ret
} }
func getScenePartial(scene *models.Scene, scraped *models.ScrapedScene, fieldOptions map[string]*models.IdentifyFieldOptionsInput, setOrganized bool) models.ScenePartial { func getScenePartial(scene *models.Scene, scraped *scraper.ScrapedScene, fieldOptions map[string]*FieldOptions, setOrganized bool) models.ScenePartial {
partial := models.ScenePartial{ partial := models.ScenePartial{
ID: scene.ID, ID: scene.ID,
} }
@ -259,17 +260,17 @@ func getScenePartial(scene *models.Scene, scraped *models.ScrapedScene, fieldOpt
return partial return partial
} }
func shouldSetSingleValueField(strategy *models.IdentifyFieldOptionsInput, hasExistingValue bool) bool { func shouldSetSingleValueField(strategy *FieldOptions, hasExistingValue bool) bool {
// if unset then default to MERGE // if unset then default to MERGE
fs := models.IdentifyFieldStrategyMerge fs := FieldStrategyMerge
if strategy != nil && strategy.Strategy.IsValid() { if strategy != nil && strategy.Strategy.IsValid() {
fs = strategy.Strategy fs = strategy.Strategy
} }
if fs == models.IdentifyFieldStrategyIgnore { if fs == FieldStrategyIgnore {
return false return false
} }
return !hasExistingValue || fs == models.IdentifyFieldStrategyOverwrite return !hasExistingValue || fs == FieldStrategyOverwrite
} }

View file

@ -8,16 +8,17 @@ import (
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/mocks" "github.com/stashapp/stash/pkg/models/mocks"
"github.com/stashapp/stash/pkg/scraper"
"github.com/stashapp/stash/pkg/sliceutil/intslice" "github.com/stashapp/stash/pkg/sliceutil/intslice"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
type mockSceneScraper struct { type mockSceneScraper struct {
errIDs []int errIDs []int
results map[int]*models.ScrapedScene results map[int]*scraper.ScrapedScene
} }
func (s mockSceneScraper) ScrapeScene(ctx context.Context, sceneID int) (*models.ScrapedScene, error) { func (s mockSceneScraper) ScrapeScene(ctx context.Context, sceneID int) (*scraper.ScrapedScene, error) {
if intslice.IntInclude(s.errIDs, sceneID) { if intslice.IntInclude(s.errIDs, sceneID) {
return nil, errors.New("scrape scene error") return nil, errors.New("scrape scene error")
} }
@ -42,12 +43,12 @@ func TestSceneIdentifier_Identify(t *testing.T) {
var scrapedTitle = "scrapedTitle" var scrapedTitle = "scrapedTitle"
defaultOptions := &models.IdentifyMetadataOptionsInput{} defaultOptions := &MetadataOptions{}
sources := []ScraperSource{ sources := []ScraperSource{
{ {
Scraper: mockSceneScraper{ Scraper: mockSceneScraper{
errIDs: []int{errID1}, errIDs: []int{errID1},
results: map[int]*models.ScrapedScene{ results: map[int]*scraper.ScrapedScene{
found1ID: { found1ID: {
Title: &scrapedTitle, Title: &scrapedTitle,
}, },
@ -57,7 +58,7 @@ func TestSceneIdentifier_Identify(t *testing.T) {
{ {
Scraper: mockSceneScraper{ Scraper: mockSceneScraper{
errIDs: []int{errID2}, errIDs: []int{errID2},
results: map[int]*models.ScrapedScene{ results: map[int]*scraper.ScrapedScene{
found2ID: { found2ID: {
Title: &scrapedTitle, Title: &scrapedTitle,
}, },
@ -150,7 +151,7 @@ func TestSceneIdentifier_modifyScene(t *testing.T) {
args{ args{
&models.Scene{}, &models.Scene{},
&scrapeResult{ &scrapeResult{
result: &models.ScrapedScene{}, result: &scraper.ScrapedScene{},
}, },
}, },
false, false,
@ -173,55 +174,55 @@ func Test_getFieldOptions(t *testing.T) {
) )
type args struct { type args struct {
options []models.IdentifyMetadataOptionsInput options []MetadataOptions
} }
tests := []struct { tests := []struct {
name string name string
args args args args
want map[string]*models.IdentifyFieldOptionsInput want map[string]*FieldOptions
}{ }{
{ {
"simple", "simple",
args{ args{
[]models.IdentifyMetadataOptionsInput{ []MetadataOptions{
{ {
FieldOptions: []*models.IdentifyFieldOptionsInput{ FieldOptions: []*FieldOptions{
{ {
Field: inFirst, Field: inFirst,
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}, },
{ {
Field: inBoth, Field: inBoth,
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}, },
}, },
}, },
{ {
FieldOptions: []*models.IdentifyFieldOptionsInput{ FieldOptions: []*FieldOptions{
{ {
Field: inSecond, Field: inSecond,
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
}, },
{ {
Field: inBoth, Field: inBoth,
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
}, },
}, },
}, },
}, },
}, },
map[string]*models.IdentifyFieldOptionsInput{ map[string]*FieldOptions{
inFirst: { inFirst: {
Field: inFirst, Field: inFirst,
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}, },
inSecond: { inSecond: {
Field: inSecond, Field: inSecond,
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
}, },
inBoth: { inBoth: {
Field: inBoth, Field: inBoth,
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}, },
}, },
}, },
@ -275,22 +276,22 @@ func Test_getScenePartial(t *testing.T) {
URL: models.NullStringPtr(scrapedURL), URL: models.NullStringPtr(scrapedURL),
} }
scrapedScene := &models.ScrapedScene{ scrapedScene := &scraper.ScrapedScene{
Title: &scrapedTitle, Title: &scrapedTitle,
Date: &scrapedDate, Date: &scrapedDate,
Details: &scrapedDetails, Details: &scrapedDetails,
URL: &scrapedURL, URL: &scrapedURL,
} }
scrapedUnchangedScene := &models.ScrapedScene{ scrapedUnchangedScene := &scraper.ScrapedScene{
Title: &originalTitle, Title: &originalTitle,
Date: &originalDate, Date: &originalDate,
Details: &originalDetails, Details: &originalDetails,
URL: &originalURL, URL: &originalURL,
} }
makeFieldOptions := func(input *models.IdentifyFieldOptionsInput) map[string]*models.IdentifyFieldOptionsInput { makeFieldOptions := func(input *FieldOptions) map[string]*FieldOptions {
return map[string]*models.IdentifyFieldOptionsInput{ return map[string]*FieldOptions{
"title": input, "title": input,
"date": input, "date": input,
"details": input, "details": input,
@ -298,22 +299,22 @@ func Test_getScenePartial(t *testing.T) {
} }
} }
overwriteAll := makeFieldOptions(&models.IdentifyFieldOptionsInput{ overwriteAll := makeFieldOptions(&FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
}) })
ignoreAll := makeFieldOptions(&models.IdentifyFieldOptionsInput{ ignoreAll := makeFieldOptions(&FieldOptions{
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}) })
mergeAll := makeFieldOptions(&models.IdentifyFieldOptionsInput{ mergeAll := makeFieldOptions(&FieldOptions{
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
}) })
setOrganised := true setOrganised := true
type args struct { type args struct {
scene *models.Scene scene *models.Scene
scraped *models.ScrapedScene scraped *scraper.ScrapedScene
fieldOptions map[string]*models.IdentifyFieldOptionsInput fieldOptions map[string]*FieldOptions
setOrganized bool setOrganized bool
} }
tests := []struct { tests := []struct {
@ -407,7 +408,7 @@ func Test_shouldSetSingleValueField(t *testing.T) {
const invalid = "invalid" const invalid = "invalid"
type args struct { type args struct {
strategy *models.IdentifyFieldOptionsInput strategy *FieldOptions
hasExistingValue bool hasExistingValue bool
} }
tests := []struct { tests := []struct {
@ -418,8 +419,8 @@ func Test_shouldSetSingleValueField(t *testing.T) {
{ {
"ignore", "ignore",
args{ args{
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}, },
false, false,
}, },
@ -428,8 +429,8 @@ func Test_shouldSetSingleValueField(t *testing.T) {
{ {
"merge existing", "merge existing",
args{ args{
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
}, },
true, true,
}, },
@ -438,8 +439,8 @@ func Test_shouldSetSingleValueField(t *testing.T) {
{ {
"merge absent", "merge absent",
args{ args{
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
}, },
false, false,
}, },
@ -448,8 +449,8 @@ func Test_shouldSetSingleValueField(t *testing.T) {
{ {
"overwrite", "overwrite",
args{ args{
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
}, },
true, true,
}, },
@ -458,7 +459,7 @@ func Test_shouldSetSingleValueField(t *testing.T) {
{ {
"nil (merge) existing", "nil (merge) existing",
args{ args{
&models.IdentifyFieldOptionsInput{}, &FieldOptions{},
true, true,
}, },
false, false,
@ -466,7 +467,7 @@ func Test_shouldSetSingleValueField(t *testing.T) {
{ {
"nil (merge) absent", "nil (merge) absent",
args{ args{
&models.IdentifyFieldOptionsInput{}, &FieldOptions{},
false, false,
}, },
true, true,
@ -474,7 +475,7 @@ func Test_shouldSetSingleValueField(t *testing.T) {
{ {
"invalid (merge) existing", "invalid (merge) existing",
args{ args{
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: invalid, Strategy: invalid,
}, },
true, true,
@ -484,7 +485,7 @@ func Test_shouldSetSingleValueField(t *testing.T) {
{ {
"invalid (merge) absent", "invalid (merge) absent",
args{ args{
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: invalid, Strategy: invalid,
}, },
false, false,

View file

@ -0,0 +1,92 @@
package identify
import (
"fmt"
"io"
"strconv"
"github.com/stashapp/stash/pkg/scraper"
)
type Source struct {
Source *scraper.Source `json:"source"`
// Options defined for a source override the defaults
Options *MetadataOptions `json:"options"`
}
type Options struct {
// An ordered list of sources to identify items with. Only the first source that finds a match is used.
Sources []*Source `json:"sources"`
// Options defined here override the configured defaults
Options *MetadataOptions `json:"options"`
// scene ids to identify
SceneIDs []string `json:"sceneIDs"`
// paths of scenes to identify - ignored if scene ids are set
Paths []string `json:"paths"`
}
type MetadataOptions struct {
// any fields missing from here are defaulted to MERGE and createMissing false
FieldOptions []*FieldOptions `json:"fieldOptions"`
// defaults to true if not provided
SetCoverImage *bool `json:"setCoverImage"`
SetOrganized *bool `json:"setOrganized"`
// defaults to true if not provided
IncludeMalePerformers *bool `json:"includeMalePerformers"`
}
type FieldOptions struct {
Field string `json:"field"`
Strategy FieldStrategy `json:"strategy"`
// creates missing objects if needed - only applicable for performers, tags and studios
CreateMissing *bool `json:"createMissing"`
}
type FieldStrategy string
const (
// Never sets the field value
FieldStrategyIgnore FieldStrategy = "IGNORE"
// For multi-value fields, merge with existing.
// For single-value fields, ignore if already set
FieldStrategyMerge FieldStrategy = "MERGE"
// Always replaces the value if a value is found.
// For multi-value fields, any existing values are removed and replaced with the
// scraped values.
FieldStrategyOverwrite FieldStrategy = "OVERWRITE"
)
var AllFieldStrategy = []FieldStrategy{
FieldStrategyIgnore,
FieldStrategyMerge,
FieldStrategyOverwrite,
}
func (e FieldStrategy) IsValid() bool {
switch e {
case FieldStrategyIgnore, FieldStrategyMerge, FieldStrategyOverwrite:
return true
}
return false
}
func (e FieldStrategy) String() string {
return string(e)
}
func (e *FieldStrategy) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = FieldStrategy(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid IdentifyFieldStrategy", str)
}
return nil
}
func (e FieldStrategy) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}

View file

@ -18,7 +18,7 @@ type sceneRelationships struct {
repo models.Repository repo models.Repository
scene *models.Scene scene *models.Scene
result *scrapeResult result *scrapeResult
fieldOptions map[string]*models.IdentifyFieldOptionsInput fieldOptions map[string]*FieldOptions
} }
func (g sceneRelationships) studio() (*int64, error) { func (g sceneRelationships) studio() (*int64, error) {
@ -61,7 +61,7 @@ func (g sceneRelationships) performers(ignoreMale bool) ([]int, error) {
} }
createMissing := fieldStrategy != nil && utils.IsTrue(fieldStrategy.CreateMissing) createMissing := fieldStrategy != nil && utils.IsTrue(fieldStrategy.CreateMissing)
strategy := models.IdentifyFieldStrategyMerge strategy := FieldStrategyMerge
if fieldStrategy != nil { if fieldStrategy != nil {
strategy = fieldStrategy.Strategy strategy = fieldStrategy.Strategy
} }
@ -75,7 +75,7 @@ func (g sceneRelationships) performers(ignoreMale bool) ([]int, error) {
return nil, fmt.Errorf("error getting scene performers: %w", err) return nil, fmt.Errorf("error getting scene performers: %w", err)
} }
if strategy == models.IdentifyFieldStrategyMerge { if strategy == FieldStrategyMerge {
// add to existing // add to existing
performerIDs = originalPerformerIDs performerIDs = originalPerformerIDs
} }
@ -115,7 +115,7 @@ func (g sceneRelationships) tags() ([]int, error) {
} }
createMissing := fieldStrategy != nil && utils.IsTrue(fieldStrategy.CreateMissing) createMissing := fieldStrategy != nil && utils.IsTrue(fieldStrategy.CreateMissing)
strategy := models.IdentifyFieldStrategyMerge strategy := FieldStrategyMerge
if fieldStrategy != nil { if fieldStrategy != nil {
strategy = fieldStrategy.Strategy strategy = fieldStrategy.Strategy
} }
@ -126,7 +126,7 @@ func (g sceneRelationships) tags() ([]int, error) {
return nil, fmt.Errorf("error getting scene tags: %w", err) return nil, fmt.Errorf("error getting scene tags: %w", err)
} }
if strategy == models.IdentifyFieldStrategyMerge { if strategy == FieldStrategyMerge {
// add to existing // add to existing
tagIDs = originalTagIDs tagIDs = originalTagIDs
} }
@ -176,7 +176,7 @@ func (g sceneRelationships) stashIDs() ([]models.StashID, error) {
return nil, nil return nil, nil
} }
strategy := models.IdentifyFieldStrategyMerge strategy := FieldStrategyMerge
if fieldStrategy != nil { if fieldStrategy != nil {
strategy = fieldStrategy.Strategy strategy = fieldStrategy.Strategy
} }
@ -193,7 +193,7 @@ func (g sceneRelationships) stashIDs() ([]models.StashID, error) {
originalStashIDs = append(originalStashIDs, *stashID) originalStashIDs = append(originalStashIDs, *stashID)
} }
if strategy == models.IdentifyFieldStrategyMerge { if strategy == FieldStrategyMerge {
// add to existing // add to existing
stashIDs = originalStashIDs stashIDs = originalStashIDs
} }

View file

@ -9,6 +9,7 @@ import (
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/models/mocks" "github.com/stashapp/stash/pkg/models/mocks"
"github.com/stashapp/stash/pkg/scraper"
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
@ -19,8 +20,8 @@ func Test_sceneRelationships_studio(t *testing.T) {
invalidStoredID := "invalidStoredID" invalidStoredID := "invalidStoredID"
createMissing := true createMissing := true
defaultOptions := &models.IdentifyFieldOptionsInput{ defaultOptions := &FieldOptions{
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
} }
repo := mocks.NewTransactionManager() repo := mocks.NewTransactionManager()
@ -30,13 +31,13 @@ func Test_sceneRelationships_studio(t *testing.T) {
tr := sceneRelationships{ tr := sceneRelationships{
repo: repo, repo: repo,
fieldOptions: make(map[string]*models.IdentifyFieldOptionsInput), fieldOptions: make(map[string]*FieldOptions),
} }
tests := []struct { tests := []struct {
name string name string
scene *models.Scene scene *models.Scene
fieldOptions *models.IdentifyFieldOptionsInput fieldOptions *FieldOptions
result *models.ScrapedStudio result *models.ScrapedStudio
want *int64 want *int64
wantErr bool wantErr bool
@ -52,8 +53,8 @@ func Test_sceneRelationships_studio(t *testing.T) {
{ {
"ignore", "ignore",
&models.Scene{}, &models.Scene{},
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}, },
&models.ScrapedStudio{ &models.ScrapedStudio{
StoredID: &validStoredID, StoredID: &validStoredID,
@ -104,8 +105,8 @@ func Test_sceneRelationships_studio(t *testing.T) {
{ {
"create missing", "create missing",
&models.Scene{}, &models.Scene{},
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
CreateMissing: &createMissing, CreateMissing: &createMissing,
}, },
&models.ScrapedStudio{}, &models.ScrapedStudio{},
@ -118,7 +119,7 @@ func Test_sceneRelationships_studio(t *testing.T) {
tr.scene = tt.scene tr.scene = tt.scene
tr.fieldOptions["studio"] = tt.fieldOptions tr.fieldOptions["studio"] = tt.fieldOptions
tr.result = &scrapeResult{ tr.result = &scrapeResult{
result: &models.ScrapedScene{ result: &scraper.ScrapedScene{
Studio: tt.result, Studio: tt.result,
}, },
} }
@ -151,8 +152,8 @@ func Test_sceneRelationships_performers(t *testing.T) {
female := models.GenderEnumFemale.String() female := models.GenderEnumFemale.String()
male := models.GenderEnumMale.String() male := models.GenderEnumMale.String()
defaultOptions := &models.IdentifyFieldOptionsInput{ defaultOptions := &FieldOptions{
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
} }
repo := mocks.NewTransactionManager() repo := mocks.NewTransactionManager()
@ -162,13 +163,13 @@ func Test_sceneRelationships_performers(t *testing.T) {
tr := sceneRelationships{ tr := sceneRelationships{
repo: repo, repo: repo,
fieldOptions: make(map[string]*models.IdentifyFieldOptionsInput), fieldOptions: make(map[string]*FieldOptions),
} }
tests := []struct { tests := []struct {
name string name string
sceneID int sceneID int
fieldOptions *models.IdentifyFieldOptionsInput fieldOptions *FieldOptions
scraped []*models.ScrapedPerformer scraped []*models.ScrapedPerformer
ignoreMale bool ignoreMale bool
want []int want []int
@ -177,8 +178,8 @@ func Test_sceneRelationships_performers(t *testing.T) {
{ {
"ignore", "ignore",
sceneID, sceneID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}, },
[]*models.ScrapedPerformer{ []*models.ScrapedPerformer{
{ {
@ -255,8 +256,8 @@ func Test_sceneRelationships_performers(t *testing.T) {
{ {
"overwrite", "overwrite",
sceneWithPerformerID, sceneWithPerformerID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
}, },
[]*models.ScrapedPerformer{ []*models.ScrapedPerformer{
{ {
@ -271,8 +272,8 @@ func Test_sceneRelationships_performers(t *testing.T) {
{ {
"ignore male (not male)", "ignore male (not male)",
sceneWithPerformerID, sceneWithPerformerID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
}, },
[]*models.ScrapedPerformer{ []*models.ScrapedPerformer{
{ {
@ -288,8 +289,8 @@ func Test_sceneRelationships_performers(t *testing.T) {
{ {
"error getting tag ID", "error getting tag ID",
sceneID, sceneID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
CreateMissing: &createMissing, CreateMissing: &createMissing,
}, },
[]*models.ScrapedPerformer{ []*models.ScrapedPerformer{
@ -310,7 +311,7 @@ func Test_sceneRelationships_performers(t *testing.T) {
} }
tr.fieldOptions["performers"] = tt.fieldOptions tr.fieldOptions["performers"] = tt.fieldOptions
tr.result = &scrapeResult{ tr.result = &scrapeResult{
result: &models.ScrapedScene{ result: &scraper.ScrapedScene{
Performers: tt.scraped, Performers: tt.scraped,
}, },
} }
@ -342,8 +343,8 @@ func Test_sceneRelationships_tags(t *testing.T) {
validName := "validName" validName := "validName"
invalidName := "invalidName" invalidName := "invalidName"
defaultOptions := &models.IdentifyFieldOptionsInput{ defaultOptions := &FieldOptions{
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
} }
repo := mocks.NewTransactionManager() repo := mocks.NewTransactionManager()
@ -362,13 +363,13 @@ func Test_sceneRelationships_tags(t *testing.T) {
tr := sceneRelationships{ tr := sceneRelationships{
repo: repo, repo: repo,
fieldOptions: make(map[string]*models.IdentifyFieldOptionsInput), fieldOptions: make(map[string]*FieldOptions),
} }
tests := []struct { tests := []struct {
name string name string
sceneID int sceneID int
fieldOptions *models.IdentifyFieldOptionsInput fieldOptions *FieldOptions
scraped []*models.ScrapedTag scraped []*models.ScrapedTag
want []int want []int
wantErr bool wantErr bool
@ -376,8 +377,8 @@ func Test_sceneRelationships_tags(t *testing.T) {
{ {
"ignore", "ignore",
sceneID, sceneID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}, },
[]*models.ScrapedTag{ []*models.ScrapedTag{
{ {
@ -434,8 +435,8 @@ func Test_sceneRelationships_tags(t *testing.T) {
{ {
"overwrite", "overwrite",
sceneWithTagID, sceneWithTagID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
}, },
[]*models.ScrapedTag{ []*models.ScrapedTag{
{ {
@ -449,8 +450,8 @@ func Test_sceneRelationships_tags(t *testing.T) {
{ {
"error getting tag ID", "error getting tag ID",
sceneID, sceneID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
}, },
[]*models.ScrapedTag{ []*models.ScrapedTag{
{ {
@ -464,8 +465,8 @@ func Test_sceneRelationships_tags(t *testing.T) {
{ {
"create missing", "create missing",
sceneID, sceneID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
CreateMissing: &createMissing, CreateMissing: &createMissing,
}, },
[]*models.ScrapedTag{ []*models.ScrapedTag{
@ -479,8 +480,8 @@ func Test_sceneRelationships_tags(t *testing.T) {
{ {
"error creating", "error creating",
sceneID, sceneID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
CreateMissing: &createMissing, CreateMissing: &createMissing,
}, },
[]*models.ScrapedTag{ []*models.ScrapedTag{
@ -499,7 +500,7 @@ func Test_sceneRelationships_tags(t *testing.T) {
} }
tr.fieldOptions["tags"] = tt.fieldOptions tr.fieldOptions["tags"] = tt.fieldOptions
tr.result = &scrapeResult{ tr.result = &scrapeResult{
result: &models.ScrapedScene{ result: &scraper.ScrapedScene{
Tags: tt.scraped, Tags: tt.scraped,
}, },
} }
@ -529,8 +530,8 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
remoteSiteID := "remoteSiteID" remoteSiteID := "remoteSiteID"
newRemoteSiteID := "newRemoteSiteID" newRemoteSiteID := "newRemoteSiteID"
defaultOptions := &models.IdentifyFieldOptionsInput{ defaultOptions := &FieldOptions{
Strategy: models.IdentifyFieldStrategyMerge, Strategy: FieldStrategyMerge,
} }
repo := mocks.NewTransactionManager() repo := mocks.NewTransactionManager()
@ -545,13 +546,13 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
tr := sceneRelationships{ tr := sceneRelationships{
repo: repo, repo: repo,
fieldOptions: make(map[string]*models.IdentifyFieldOptionsInput), fieldOptions: make(map[string]*FieldOptions),
} }
tests := []struct { tests := []struct {
name string name string
sceneID int sceneID int
fieldOptions *models.IdentifyFieldOptionsInput fieldOptions *FieldOptions
endpoint string endpoint string
remoteSiteID *string remoteSiteID *string
want []models.StashID want []models.StashID
@ -560,8 +561,8 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
{ {
"ignore", "ignore",
sceneID, sceneID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyIgnore, Strategy: FieldStrategyIgnore,
}, },
newEndpoint, newEndpoint,
&remoteSiteID, &remoteSiteID,
@ -639,8 +640,8 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
{ {
"overwrite", "overwrite",
sceneWithStashID, sceneWithStashID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
}, },
newEndpoint, newEndpoint,
&newRemoteSiteID, &newRemoteSiteID,
@ -655,8 +656,8 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
{ {
"overwrite same", "overwrite same",
sceneWithStashID, sceneWithStashID,
&models.IdentifyFieldOptionsInput{ &FieldOptions{
Strategy: models.IdentifyFieldStrategyOverwrite, Strategy: FieldStrategyOverwrite,
}, },
existingEndpoint, existingEndpoint,
&remoteSiteID, &remoteSiteID,
@ -674,7 +675,7 @@ func Test_sceneRelationships_stashIDs(t *testing.T) {
source: ScraperSource{ source: ScraperSource{
RemoteSite: tt.endpoint, RemoteSite: tt.endpoint,
}, },
result: &models.ScrapedScene{ result: &scraper.ScrapedScene{
RemoteSiteID: tt.remoteSiteID, RemoteSiteID: tt.remoteSiteID,
}, },
} }
@ -712,7 +713,7 @@ func Test_sceneRelationships_cover(t *testing.T) {
tr := sceneRelationships{ tr := sceneRelationships{
repo: repo, repo: repo,
fieldOptions: make(map[string]*models.IdentifyFieldOptionsInput), fieldOptions: make(map[string]*FieldOptions),
} }
tests := []struct { tests := []struct {
@ -764,7 +765,7 @@ func Test_sceneRelationships_cover(t *testing.T) {
ID: tt.sceneID, ID: tt.sceneID,
} }
tr.result = &scrapeResult{ tr.result = &scrapeResult{
result: &models.ScrapedScene{ result: &scraper.ScrapedScene{
Image: tt.image, Image: tt.image,
}, },
} }

View file

@ -15,6 +15,7 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/stashapp/stash/internal/identify"
"github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/fsutil"
"github.com/stashapp/stash/pkg/hash" "github.com/stashapp/stash/pkg/hash"
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
@ -147,10 +148,10 @@ const (
// Image lightbox options // Image lightbox options
legacyImageLightboxSlideshowDelay = "slideshow_delay" legacyImageLightboxSlideshowDelay = "slideshow_delay"
ImageLightboxSlideshowDelay = "image_lightbox.slideshow_delay" ImageLightboxSlideshowDelay = "image_lightbox.slideshow_delay"
ImageLightboxDisplayMode = "image_lightbox.display_mode" ImageLightboxDisplayModeKey = "image_lightbox.display_mode"
ImageLightboxScaleUp = "image_lightbox.scale_up" ImageLightboxScaleUp = "image_lightbox.scale_up"
ImageLightboxResetZoomOnNav = "image_lightbox.reset_zoom_on_nav" ImageLightboxResetZoomOnNav = "image_lightbox.reset_zoom_on_nav"
ImageLightboxScrollMode = "image_lightbox.scroll_mode" ImageLightboxScrollModeKey = "image_lightbox.scroll_mode"
ImageLightboxScrollAttemptsBeforeChange = "image_lightbox.scroll_attempts_before_change" ImageLightboxScrollAttemptsBeforeChange = "image_lightbox.scroll_attempts_before_change"
UI = "ui" UI = "ui"
@ -460,14 +461,27 @@ func (i *Instance) getStringMapString(key string) map[string]string {
return i.viper(key).GetStringMapString(key) return i.viper(key).GetStringMapString(key)
} }
type StashConfig struct {
Path string `json:"path"`
ExcludeVideo bool `json:"excludeVideo"`
ExcludeImage bool `json:"excludeImage"`
}
// Stash configuration details
type StashConfigInput struct {
Path string `json:"path"`
ExcludeVideo bool `json:"excludeVideo"`
ExcludeImage bool `json:"excludeImage"`
}
// GetStathPaths returns the configured stash library paths. // GetStathPaths returns the configured stash library paths.
// Works opposite to the usual case - it will return the override // Works opposite to the usual case - it will return the override
// value only if the main value is not set. // value only if the main value is not set.
func (i *Instance) GetStashPaths() []*models.StashConfig { func (i *Instance) GetStashPaths() []*StashConfig {
i.RLock() i.RLock()
defer i.RUnlock() defer i.RUnlock()
var ret []*models.StashConfig var ret []*StashConfig
v := i.main v := i.main
if !v.IsSet(Stash) { if !v.IsSet(Stash) {
@ -479,7 +493,7 @@ func (i *Instance) GetStashPaths() []*models.StashConfig {
ss := v.GetStringSlice(Stash) ss := v.GetStringSlice(Stash)
ret = nil ret = nil
for _, path := range ss { for _, path := range ss {
toAdd := &models.StashConfig{ toAdd := &StashConfig{
Path: path, Path: path,
} }
ret = append(ret, toAdd) ret = append(ret, toAdd)
@ -610,8 +624,8 @@ func (i *Instance) GetScraperExcludeTagPatterns() []string {
return i.getStringSlice(ScraperExcludeTagPatterns) return i.getStringSlice(ScraperExcludeTagPatterns)
} }
func (i *Instance) GetStashBoxes() models.StashBoxes { func (i *Instance) GetStashBoxes() []*models.StashBox {
var boxes models.StashBoxes var boxes []*models.StashBox
if err := i.unmarshalKey(StashBoxes, &boxes); err != nil { if err := i.unmarshalKey(StashBoxes, &boxes); err != nil {
logger.Warnf("error in unmarshalkey: %v", err) logger.Warnf("error in unmarshalkey: %v", err)
} }
@ -797,7 +811,13 @@ func (i *Instance) ValidateCredentials(username string, password string) bool {
var stashBoxRe = regexp.MustCompile("^http.*graphql$") var stashBoxRe = regexp.MustCompile("^http.*graphql$")
func (i *Instance) ValidateStashBoxes(boxes []*models.StashBoxInput) error { type StashBoxInput struct {
Endpoint string `json:"endpoint"`
APIKey string `json:"api_key"`
Name string `json:"name"`
}
func (i *Instance) ValidateStashBoxes(boxes []*StashBoxInput) error {
isMulti := len(boxes) > 1 isMulti := len(boxes) > 1
for _, box := range boxes { for _, box := range boxes {
@ -933,18 +953,18 @@ func (i *Instance) getSlideshowDelay() int {
return ret return ret
} }
func (i *Instance) GetImageLightboxOptions() models.ConfigImageLightboxResult { func (i *Instance) GetImageLightboxOptions() ConfigImageLightboxResult {
i.RLock() i.RLock()
defer i.RUnlock() defer i.RUnlock()
delay := i.getSlideshowDelay() delay := i.getSlideshowDelay()
ret := models.ConfigImageLightboxResult{ ret := ConfigImageLightboxResult{
SlideshowDelay: &delay, SlideshowDelay: &delay,
} }
if v := i.viperWith(ImageLightboxDisplayMode); v != nil { if v := i.viperWith(ImageLightboxDisplayModeKey); v != nil {
mode := models.ImageLightboxDisplayMode(v.GetString(ImageLightboxDisplayMode)) mode := ImageLightboxDisplayMode(v.GetString(ImageLightboxDisplayModeKey))
ret.DisplayMode = &mode ret.DisplayMode = &mode
} }
if v := i.viperWith(ImageLightboxScaleUp); v != nil { if v := i.viperWith(ImageLightboxScaleUp); v != nil {
@ -955,8 +975,8 @@ func (i *Instance) GetImageLightboxOptions() models.ConfigImageLightboxResult {
value := v.GetBool(ImageLightboxResetZoomOnNav) value := v.GetBool(ImageLightboxResetZoomOnNav)
ret.ResetZoomOnNav = &value ret.ResetZoomOnNav = &value
} }
if v := i.viperWith(ImageLightboxScrollMode); v != nil { if v := i.viperWith(ImageLightboxScrollModeKey); v != nil {
mode := models.ImageLightboxScrollMode(v.GetString(ImageLightboxScrollMode)) mode := ImageLightboxScrollMode(v.GetString(ImageLightboxScrollModeKey))
ret.ScrollMode = &mode ret.ScrollMode = &mode
} }
if v := i.viperWith(ImageLightboxScrollAttemptsBeforeChange); v != nil { if v := i.viperWith(ImageLightboxScrollAttemptsBeforeChange); v != nil {
@ -966,8 +986,8 @@ func (i *Instance) GetImageLightboxOptions() models.ConfigImageLightboxResult {
return ret return ret
} }
func (i *Instance) GetDisableDropdownCreate() *models.ConfigDisableDropdownCreate { func (i *Instance) GetDisableDropdownCreate() *ConfigDisableDropdownCreate {
return &models.ConfigDisableDropdownCreate{ return &ConfigDisableDropdownCreate{
Performer: i.getBool(DisableDropdownCreatePerformer), Performer: i.getBool(DisableDropdownCreatePerformer),
Studio: i.getBool(DisableDropdownCreateStudio), Studio: i.getBool(DisableDropdownCreateStudio),
Tag: i.getBool(DisableDropdownCreateTag), Tag: i.getBool(DisableDropdownCreateTag),
@ -1056,13 +1076,13 @@ func (i *Instance) GetDeleteGeneratedDefault() bool {
// GetDefaultIdentifySettings returns the default Identify task settings. // GetDefaultIdentifySettings returns the default Identify task settings.
// Returns nil if the settings could not be unmarshalled, or if it // Returns nil if the settings could not be unmarshalled, or if it
// has not been set. // has not been set.
func (i *Instance) GetDefaultIdentifySettings() *models.IdentifyMetadataTaskOptions { func (i *Instance) GetDefaultIdentifySettings() *identify.Options {
i.RLock() i.RLock()
defer i.RUnlock() defer i.RUnlock()
v := i.viper(DefaultIdentifySettings) v := i.viper(DefaultIdentifySettings)
if v.IsSet(DefaultIdentifySettings) { if v.IsSet(DefaultIdentifySettings) {
var ret models.IdentifyMetadataTaskOptions var ret identify.Options
if err := v.UnmarshalKey(DefaultIdentifySettings, &ret); err != nil { if err := v.UnmarshalKey(DefaultIdentifySettings, &ret); err != nil {
return nil return nil
} }
@ -1075,13 +1095,13 @@ func (i *Instance) GetDefaultIdentifySettings() *models.IdentifyMetadataTaskOpti
// GetDefaultScanSettings returns the default Scan task settings. // GetDefaultScanSettings returns the default Scan task settings.
// Returns nil if the settings could not be unmarshalled, or if it // Returns nil if the settings could not be unmarshalled, or if it
// has not been set. // has not been set.
func (i *Instance) GetDefaultScanSettings() *models.ScanMetadataOptions { func (i *Instance) GetDefaultScanSettings() *ScanMetadataOptions {
i.RLock() i.RLock()
defer i.RUnlock() defer i.RUnlock()
v := i.viper(DefaultScanSettings) v := i.viper(DefaultScanSettings)
if v.IsSet(DefaultScanSettings) { if v.IsSet(DefaultScanSettings) {
var ret models.ScanMetadataOptions var ret ScanMetadataOptions
if err := v.UnmarshalKey(DefaultScanSettings, &ret); err != nil { if err := v.UnmarshalKey(DefaultScanSettings, &ret); err != nil {
return nil return nil
} }
@ -1094,13 +1114,13 @@ func (i *Instance) GetDefaultScanSettings() *models.ScanMetadataOptions {
// GetDefaultAutoTagSettings returns the default Scan task settings. // GetDefaultAutoTagSettings returns the default Scan task settings.
// Returns nil if the settings could not be unmarshalled, or if it // Returns nil if the settings could not be unmarshalled, or if it
// has not been set. // has not been set.
func (i *Instance) GetDefaultAutoTagSettings() *models.AutoTagMetadataOptions { func (i *Instance) GetDefaultAutoTagSettings() *AutoTagMetadataOptions {
i.RLock() i.RLock()
defer i.RUnlock() defer i.RUnlock()
v := i.viper(DefaultAutoTagSettings) v := i.viper(DefaultAutoTagSettings)
if v.IsSet(DefaultAutoTagSettings) { if v.IsSet(DefaultAutoTagSettings) {
var ret models.AutoTagMetadataOptions var ret AutoTagMetadataOptions
if err := v.UnmarshalKey(DefaultAutoTagSettings, &ret); err != nil { if err := v.UnmarshalKey(DefaultAutoTagSettings, &ret); err != nil {
return nil return nil
} }

View file

@ -0,0 +1,27 @@
package config
type ScanMetadataOptions struct {
// Set name, date, details from metadata (if present)
UseFileMetadata bool `json:"useFileMetadata"`
// Strip file extension from title
StripFileExtension bool `json:"stripFileExtension"`
// Generate previews during scan
ScanGeneratePreviews bool `json:"scanGeneratePreviews"`
// Generate image previews during scan
ScanGenerateImagePreviews bool `json:"scanGenerateImagePreviews"`
// Generate sprites during scan
ScanGenerateSprites bool `json:"scanGenerateSprites"`
// Generate phashes during scan
ScanGeneratePhashes bool `json:"scanGeneratePhashes"`
// Generate image thumbnails during scan
ScanGenerateThumbnails bool `json:"scanGenerateThumbnails"`
}
type AutoTagMetadataOptions struct {
// IDs of performers to tag files with, or "*" for all
Performers []string `json:"performers"`
// IDs of studios to tag files with, or "*" for all
Studios []string `json:"studios"`
// IDs of tags to tag files with, or "*" for all
Tags []string `json:"tags"`
}

View file

@ -0,0 +1,106 @@
package config
import (
"fmt"
"io"
"strconv"
)
type ConfigImageLightboxResult struct {
SlideshowDelay *int `json:"slideshowDelay"`
DisplayMode *ImageLightboxDisplayMode `json:"displayMode"`
ScaleUp *bool `json:"scaleUp"`
ResetZoomOnNav *bool `json:"resetZoomOnNav"`
ScrollMode *ImageLightboxScrollMode `json:"scrollMode"`
ScrollAttemptsBeforeChange int `json:"scrollAttemptsBeforeChange"`
}
type ImageLightboxDisplayMode string
const (
ImageLightboxDisplayModeOriginal ImageLightboxDisplayMode = "ORIGINAL"
ImageLightboxDisplayModeFitXy ImageLightboxDisplayMode = "FIT_XY"
ImageLightboxDisplayModeFitX ImageLightboxDisplayMode = "FIT_X"
)
var AllImageLightboxDisplayMode = []ImageLightboxDisplayMode{
ImageLightboxDisplayModeOriginal,
ImageLightboxDisplayModeFitXy,
ImageLightboxDisplayModeFitX,
}
func (e ImageLightboxDisplayMode) IsValid() bool {
switch e {
case ImageLightboxDisplayModeOriginal, ImageLightboxDisplayModeFitXy, ImageLightboxDisplayModeFitX:
return true
}
return false
}
func (e ImageLightboxDisplayMode) String() string {
return string(e)
}
func (e *ImageLightboxDisplayMode) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = ImageLightboxDisplayMode(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ImageLightboxDisplayMode", str)
}
return nil
}
func (e ImageLightboxDisplayMode) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type ImageLightboxScrollMode string
const (
ImageLightboxScrollModeZoom ImageLightboxScrollMode = "ZOOM"
ImageLightboxScrollModePanY ImageLightboxScrollMode = "PAN_Y"
)
var AllImageLightboxScrollMode = []ImageLightboxScrollMode{
ImageLightboxScrollModeZoom,
ImageLightboxScrollModePanY,
}
func (e ImageLightboxScrollMode) IsValid() bool {
switch e {
case ImageLightboxScrollModeZoom, ImageLightboxScrollModePanY:
return true
}
return false
}
func (e ImageLightboxScrollMode) String() string {
return string(e)
}
func (e *ImageLightboxScrollMode) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = ImageLightboxScrollMode(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ImageLightboxScrollMode", str)
}
return nil
}
func (e ImageLightboxScrollMode) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type ConfigDisableDropdownCreate struct {
Performer bool `json:"performer"`
Tag bool `json:"tag"`
Studio bool `json:"studio"`
}

View file

@ -16,6 +16,32 @@ import (
"github.com/stashapp/stash/pkg/tag" "github.com/stashapp/stash/pkg/tag"
) )
type SceneParserInput struct {
IgnoreWords []string `json:"ignoreWords"`
WhitespaceCharacters *string `json:"whitespaceCharacters"`
CapitalizeTitle *bool `json:"capitalizeTitle"`
IgnoreOrganized *bool `json:"ignoreOrganized"`
}
type SceneParserResult struct {
Scene *models.Scene `json:"scene"`
Title *string `json:"title"`
Details *string `json:"details"`
URL *string `json:"url"`
Date *string `json:"date"`
Rating *int `json:"rating"`
StudioID *string `json:"studio_id"`
GalleryIds []string `json:"gallery_ids"`
PerformerIds []string `json:"performer_ids"`
Movies []*SceneMovieID `json:"movies"`
TagIds []string `json:"tag_ids"`
}
type SceneMovieID struct {
MovieID string `json:"movie_id"`
SceneIndex *string `json:"scene_index"`
}
type parserField struct { type parserField struct {
field string field string
fieldRegex *regexp.Regexp fieldRegex *regexp.Regexp
@ -402,7 +428,7 @@ func (m parseMapper) parse(scene *models.Scene) *sceneHolder {
type SceneFilenameParser struct { type SceneFilenameParser struct {
Pattern string Pattern string
ParserInput models.SceneParserInput ParserInput SceneParserInput
Filter *models.FindFilterType Filter *models.FindFilterType
whitespaceRE *regexp.Regexp whitespaceRE *regexp.Regexp
performerCache map[string]*models.Performer performerCache map[string]*models.Performer
@ -411,7 +437,7 @@ type SceneFilenameParser struct {
tagCache map[string]*models.Tag tagCache map[string]*models.Tag
} }
func NewSceneFilenameParser(filter *models.FindFilterType, config models.SceneParserInput) *SceneFilenameParser { func NewSceneFilenameParser(filter *models.FindFilterType, config SceneParserInput) *SceneFilenameParser {
p := &SceneFilenameParser{ p := &SceneFilenameParser{
Pattern: *filter.Q, Pattern: *filter.Q,
ParserInput: config, ParserInput: config,
@ -444,7 +470,7 @@ func (p *SceneFilenameParser) initWhiteSpaceRegex() {
} }
} }
func (p *SceneFilenameParser) Parse(repo models.ReaderRepository) ([]*models.SceneParserResult, int, error) { func (p *SceneFilenameParser) Parse(repo models.ReaderRepository) ([]*SceneParserResult, int, error) {
// perform the query to find the scenes // perform the query to find the scenes
mapper, err := newParseMapper(p.Pattern, p.ParserInput.IgnoreWords) mapper, err := newParseMapper(p.Pattern, p.ParserInput.IgnoreWords)
@ -476,13 +502,13 @@ func (p *SceneFilenameParser) Parse(repo models.ReaderRepository) ([]*models.Sce
return ret, total, nil return ret, total, nil
} }
func (p *SceneFilenameParser) parseScenes(repo models.ReaderRepository, scenes []*models.Scene, mapper *parseMapper) []*models.SceneParserResult { func (p *SceneFilenameParser) parseScenes(repo models.ReaderRepository, scenes []*models.Scene, mapper *parseMapper) []*SceneParserResult {
var ret []*models.SceneParserResult var ret []*SceneParserResult
for _, scene := range scenes { for _, scene := range scenes {
sceneHolder := mapper.parse(scene) sceneHolder := mapper.parse(scene)
if sceneHolder != nil { if sceneHolder != nil {
r := &models.SceneParserResult{ r := &SceneParserResult{
Scene: scene, Scene: scene,
} }
p.setParserResult(repo, *sceneHolder, r) p.setParserResult(repo, *sceneHolder, r)
@ -589,7 +615,7 @@ func (p *SceneFilenameParser) queryTag(qb models.TagReader, tagName string) *mod
return ret return ret
} }
func (p *SceneFilenameParser) setPerformers(qb models.PerformerReader, h sceneHolder, result *models.SceneParserResult) { func (p *SceneFilenameParser) setPerformers(qb models.PerformerReader, h sceneHolder, result *SceneParserResult) {
// query for each performer // query for each performer
performersSet := make(map[int]bool) performersSet := make(map[int]bool)
for _, performerName := range h.performers { for _, performerName := range h.performers {
@ -605,7 +631,7 @@ func (p *SceneFilenameParser) setPerformers(qb models.PerformerReader, h sceneHo
} }
} }
func (p *SceneFilenameParser) setTags(qb models.TagReader, h sceneHolder, result *models.SceneParserResult) { func (p *SceneFilenameParser) setTags(qb models.TagReader, h sceneHolder, result *SceneParserResult) {
// query for each performer // query for each performer
tagsSet := make(map[int]bool) tagsSet := make(map[int]bool)
for _, tagName := range h.tags { for _, tagName := range h.tags {
@ -621,7 +647,7 @@ func (p *SceneFilenameParser) setTags(qb models.TagReader, h sceneHolder, result
} }
} }
func (p *SceneFilenameParser) setStudio(qb models.StudioReader, h sceneHolder, result *models.SceneParserResult) { func (p *SceneFilenameParser) setStudio(qb models.StudioReader, h sceneHolder, result *SceneParserResult) {
// query for each performer // query for each performer
if h.studio != "" { if h.studio != "" {
studio := p.queryStudio(qb, h.studio) studio := p.queryStudio(qb, h.studio)
@ -632,7 +658,7 @@ func (p *SceneFilenameParser) setStudio(qb models.StudioReader, h sceneHolder, r
} }
} }
func (p *SceneFilenameParser) setMovies(qb models.MovieReader, h sceneHolder, result *models.SceneParserResult) { func (p *SceneFilenameParser) setMovies(qb models.MovieReader, h sceneHolder, result *SceneParserResult) {
// query for each movie // query for each movie
moviesSet := make(map[int]bool) moviesSet := make(map[int]bool)
for _, movieName := range h.movies { for _, movieName := range h.movies {
@ -640,7 +666,7 @@ func (p *SceneFilenameParser) setMovies(qb models.MovieReader, h sceneHolder, re
movie := p.queryMovie(qb, movieName) movie := p.queryMovie(qb, movieName)
if movie != nil { if movie != nil {
if _, found := moviesSet[movie.ID]; !found { if _, found := moviesSet[movie.ID]; !found {
result.Movies = append(result.Movies, &models.SceneMovieID{ result.Movies = append(result.Movies, &SceneMovieID{
MovieID: strconv.Itoa(movie.ID), MovieID: strconv.Itoa(movie.ID),
}) })
moviesSet[movie.ID] = true moviesSet[movie.ID] = true
@ -650,7 +676,7 @@ func (p *SceneFilenameParser) setMovies(qb models.MovieReader, h sceneHolder, re
} }
} }
func (p *SceneFilenameParser) setParserResult(repo models.ReaderRepository, h sceneHolder, result *models.SceneParserResult) { func (p *SceneFilenameParser) setParserResult(repo models.ReaderRepository, h sceneHolder, result *SceneParserResult) {
if h.result.Title.Valid { if h.result.Title.Valid {
title := h.result.Title.String title := h.result.Title.String
title = p.replaceWhitespaceCharacters(title) title = p.replaceWhitespaceCharacters(title)

View file

@ -2,11 +2,55 @@ package manager
import ( import (
"fmt" "fmt"
"io"
"strconv"
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
) )
type ImportDuplicateEnum string
const (
ImportDuplicateEnumIgnore ImportDuplicateEnum = "IGNORE"
ImportDuplicateEnumOverwrite ImportDuplicateEnum = "OVERWRITE"
ImportDuplicateEnumFail ImportDuplicateEnum = "FAIL"
)
var AllImportDuplicateEnum = []ImportDuplicateEnum{
ImportDuplicateEnumIgnore,
ImportDuplicateEnumOverwrite,
ImportDuplicateEnumFail,
}
func (e ImportDuplicateEnum) IsValid() bool {
switch e {
case ImportDuplicateEnumIgnore, ImportDuplicateEnumOverwrite, ImportDuplicateEnumFail:
return true
}
return false
}
func (e ImportDuplicateEnum) String() string {
return string(e)
}
func (e *ImportDuplicateEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = ImportDuplicateEnum(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ImportDuplicateEnum", str)
}
return nil
}
func (e ImportDuplicateEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type importer interface { type importer interface {
PreImport() error PreImport() error
PostImport(id int) error PostImport(id int) error
@ -16,7 +60,7 @@ type importer interface {
Update(id int) error Update(id int) error
} }
func performImport(i importer, duplicateBehaviour models.ImportDuplicateEnum) error { func performImport(i importer, duplicateBehaviour ImportDuplicateEnum) error {
if err := i.PreImport(); err != nil { if err := i.PreImport(); err != nil {
return err return err
} }
@ -31,9 +75,9 @@ func performImport(i importer, duplicateBehaviour models.ImportDuplicateEnum) er
var id int var id int
if existing != nil { if existing != nil {
if duplicateBehaviour == models.ImportDuplicateEnumFail { if duplicateBehaviour == ImportDuplicateEnumFail {
return fmt.Errorf("existing object with name '%s'", name) return fmt.Errorf("existing object with name '%s'", name)
} else if duplicateBehaviour == models.ImportDuplicateEnumIgnore { } else if duplicateBehaviour == ImportDuplicateEnumIgnore {
logger.Info("Skipping existing object") logger.Info("Skipping existing object")
return nil return nil
} }

View file

@ -4,9 +4,11 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
"runtime/pprof" "runtime/pprof"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -30,6 +32,67 @@ import (
"github.com/stashapp/stash/ui" "github.com/stashapp/stash/ui"
) )
type SystemStatus struct {
DatabaseSchema *int `json:"databaseSchema"`
DatabasePath *string `json:"databasePath"`
ConfigPath *string `json:"configPath"`
AppSchema int `json:"appSchema"`
Status SystemStatusEnum `json:"status"`
}
type SystemStatusEnum string
const (
SystemStatusEnumSetup SystemStatusEnum = "SETUP"
SystemStatusEnumNeedsMigration SystemStatusEnum = "NEEDS_MIGRATION"
SystemStatusEnumOk SystemStatusEnum = "OK"
)
var AllSystemStatusEnum = []SystemStatusEnum{
SystemStatusEnumSetup,
SystemStatusEnumNeedsMigration,
SystemStatusEnumOk,
}
func (e SystemStatusEnum) IsValid() bool {
switch e {
case SystemStatusEnumSetup, SystemStatusEnumNeedsMigration, SystemStatusEnumOk:
return true
}
return false
}
func (e SystemStatusEnum) String() string {
return string(e)
}
func (e *SystemStatusEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = SystemStatusEnum(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid SystemStatusEnum", str)
}
return nil
}
func (e SystemStatusEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type SetupInput struct {
// Empty to indicate $HOME/.stash/config.yml default
ConfigLocation string `json:"configLocation"`
Stashes []*config.StashConfigInput `json:"stashes"`
// Empty to indicate default
DatabaseFile string `json:"databaseFile"`
// Empty to indicate default
GeneratedLocation string `json:"generatedLocation"`
}
type Manager struct { type Manager struct {
Config *config.Instance Config *config.Instance
Logger *log.Logger Logger *log.Logger
@ -354,7 +417,7 @@ func (s *Manager) RefreshScraperCache() {
s.ScraperCache = s.initScraperCache() s.ScraperCache = s.initScraperCache()
} }
func setSetupDefaults(input *models.SetupInput) { func setSetupDefaults(input *SetupInput) {
if input.ConfigLocation == "" { if input.ConfigLocation == "" {
input.ConfigLocation = filepath.Join(fsutil.GetHomeDirectory(), ".stash", "config.yml") input.ConfigLocation = filepath.Join(fsutil.GetHomeDirectory(), ".stash", "config.yml")
} }
@ -369,7 +432,7 @@ func setSetupDefaults(input *models.SetupInput) {
} }
} }
func (s *Manager) Setup(ctx context.Context, input models.SetupInput) error { func (s *Manager) Setup(ctx context.Context, input SetupInput) error {
setSetupDefaults(&input) setSetupDefaults(&input)
c := s.Config c := s.Config
@ -433,7 +496,11 @@ func (s *Manager) validateFFMPEG() error {
return nil return nil
} }
func (s *Manager) Migrate(ctx context.Context, input models.MigrateInput) error { type MigrateInput struct {
BackupPath string `json:"backupPath"`
}
func (s *Manager) Migrate(ctx context.Context, input MigrateInput) error {
// always backup so that we can roll back to the previous version if // always backup so that we can roll back to the previous version if
// migration fails // migration fails
backupPath := input.BackupPath backupPath := input.BackupPath
@ -473,20 +540,20 @@ func (s *Manager) Migrate(ctx context.Context, input models.MigrateInput) error
return nil return nil
} }
func (s *Manager) GetSystemStatus() *models.SystemStatus { func (s *Manager) GetSystemStatus() *SystemStatus {
status := models.SystemStatusEnumOk status := SystemStatusEnumOk
dbSchema := int(database.Version()) dbSchema := int(database.Version())
dbPath := database.DatabasePath() dbPath := database.DatabasePath()
appSchema := int(database.AppSchemaVersion()) appSchema := int(database.AppSchemaVersion())
configFile := s.Config.GetConfigFile() configFile := s.Config.GetConfigFile()
if s.Config.IsNewSystem() { if s.Config.IsNewSystem() {
status = models.SystemStatusEnumSetup status = SystemStatusEnumSetup
} else if dbSchema < appSchema { } else if dbSchema < appSchema {
status = models.SystemStatusEnumNeedsMigration status = SystemStatusEnumNeedsMigration
} }
return &models.SystemStatus{ return &SystemStatus{
DatabaseSchema: &dbSchema, DatabaseSchema: &dbSchema,
DatabasePath: &dbPath, DatabasePath: &dbPath,
AppSchema: appSchema, AppSchema: appSchema,

View file

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"sync" "sync"
"time"
"github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/fsutil"
@ -34,12 +35,12 @@ func isImage(pathname string) bool {
return fsutil.MatchExtension(pathname, imgExt) return fsutil.MatchExtension(pathname, imgExt)
} }
func getScanPaths(inputPaths []string) []*models.StashConfig { func getScanPaths(inputPaths []string) []*config.StashConfig {
if len(inputPaths) == 0 { if len(inputPaths) == 0 {
return config.GetInstance().GetStashPaths() return config.GetInstance().GetStashPaths()
} }
var ret []*models.StashConfig var ret []*config.StashConfig
for _, p := range inputPaths { for _, p := range inputPaths {
s := getStashFromDirPath(p) s := getStashFromDirPath(p)
if s == nil { if s == nil {
@ -62,7 +63,22 @@ func (s *Manager) ScanSubscribe(ctx context.Context) <-chan bool {
return s.scanSubs.subscribe(ctx) return s.scanSubs.subscribe(ctx)
} }
func (s *Manager) Scan(ctx context.Context, input models.ScanMetadataInput) (int, error) { type ScanMetadataInput struct {
Paths []string `json:"paths"`
config.ScanMetadataOptions
// Filter options for the scan
Filter *ScanMetaDataFilterInput `json:"filter"`
}
// Filter options for meta data scannning
type ScanMetaDataFilterInput struct {
// If set, files with a modification time before this time point are ignored by the scan
MinModTime *time.Time `json:"minModTime"`
}
func (s *Manager) Scan(ctx context.Context, input ScanMetadataInput) (int, error) {
if err := s.validateFFMPEG(); err != nil { if err := s.validateFFMPEG(); err != nil {
return 0, err return 0, err
} }
@ -88,7 +104,7 @@ func (s *Manager) Import(ctx context.Context) (int, error) {
txnManager: s.TxnManager, txnManager: s.TxnManager,
BaseDir: metadataPath, BaseDir: metadataPath,
Reset: true, Reset: true,
DuplicateBehaviour: models.ImportDuplicateEnumFail, DuplicateBehaviour: ImportDuplicateEnumFail,
MissingRefBehaviour: models.ImportMissingRefEnumFail, MissingRefBehaviour: models.ImportMissingRefEnumFail,
fileNamingAlgorithm: config.GetVideoFileNamingAlgorithm(), fileNamingAlgorithm: config.GetVideoFileNamingAlgorithm(),
} }
@ -131,7 +147,7 @@ func (s *Manager) RunSingleTask(ctx context.Context, t Task) int {
return s.JobManager.Add(ctx, t.GetDescription(), j) return s.JobManager.Add(ctx, t.GetDescription(), j)
} }
func (s *Manager) Generate(ctx context.Context, input models.GenerateMetadataInput) (int, error) { func (s *Manager) Generate(ctx context.Context, input GenerateMetadataInput) (int, error) {
if err := s.validateFFMPEG(); err != nil { if err := s.validateFFMPEG(); err != nil {
return 0, err return 0, err
} }
@ -193,7 +209,18 @@ func (s *Manager) generateScreenshot(ctx context.Context, sceneId string, at *fl
return s.JobManager.Add(ctx, fmt.Sprintf("Generating screenshot for scene id %s", sceneId), j) return s.JobManager.Add(ctx, fmt.Sprintf("Generating screenshot for scene id %s", sceneId), j)
} }
func (s *Manager) AutoTag(ctx context.Context, input models.AutoTagMetadataInput) int { type AutoTagMetadataInput struct {
// Paths to tag, null for all files
Paths []string `json:"paths"`
// IDs of performers to tag files with, or "*" for all
Performers []string `json:"performers"`
// IDs of studios to tag files with, or "*" for all
Studios []string `json:"studios"`
// IDs of tags to tag files with, or "*" for all
Tags []string `json:"tags"`
}
func (s *Manager) AutoTag(ctx context.Context, input AutoTagMetadataInput) int {
j := autoTagJob{ j := autoTagJob{
txnManager: s.TxnManager, txnManager: s.TxnManager,
input: input, input: input,
@ -202,7 +229,13 @@ func (s *Manager) AutoTag(ctx context.Context, input models.AutoTagMetadataInput
return s.JobManager.Add(ctx, "Auto-tagging...", &j) return s.JobManager.Add(ctx, "Auto-tagging...", &j)
} }
func (s *Manager) Clean(ctx context.Context, input models.CleanMetadataInput) int { type CleanMetadataInput struct {
Paths []string `json:"paths"`
// Do a dry run. Don't delete any files
DryRun bool `json:"dryRun"`
}
func (s *Manager) Clean(ctx context.Context, input CleanMetadataInput) int {
j := cleanJob{ j := cleanJob{
txnManager: s.TxnManager, txnManager: s.TxnManager,
input: input, input: input,
@ -260,7 +293,21 @@ func (s *Manager) MigrateHash(ctx context.Context) int {
return s.JobManager.Add(ctx, "Migrating scene hashes...", j) return s.JobManager.Add(ctx, "Migrating scene hashes...", j)
} }
func (s *Manager) StashBoxBatchPerformerTag(ctx context.Context, input models.StashBoxBatchPerformerTagInput) int { // If neither performer_ids nor performer_names are set, tag all performers
type StashBoxBatchPerformerTagInput struct {
// Stash endpoint to use for the performer tagging
Endpoint int `json:"endpoint"`
// Fields to exclude when executing the performer tagging
ExcludeFields []string `json:"exclude_fields"`
// Refresh performers already tagged by StashBox if true. Only tag performers with no StashBox tagging if false
Refresh bool `json:"refresh"`
// If set, only tag these performer ids
PerformerIds []string `json:"performer_ids"`
// If set, only tag these performer names
PerformerNames []string `json:"performer_names"`
}
func (s *Manager) StashBoxBatchPerformerTag(ctx context.Context, input StashBoxBatchPerformerTagInput) int {
j := job.MakeJobExec(func(ctx context.Context, progress *job.Progress) { j := job.MakeJobExec(func(ctx context.Context, progress *job.Progress) {
logger.Infof("Initiating stash-box batch performer tag") logger.Infof("Initiating stash-box batch performer tag")

View file

@ -50,20 +50,26 @@ func includeSceneStreamPath(scene *models.Scene, streamingResolution models.Stre
return int64(maxStreamingResolution.GetMinResolution()) >= minResolution return int64(maxStreamingResolution.GetMinResolution()) >= minResolution
} }
func makeStreamEndpoint(streamURL string, streamingResolution models.StreamingResolutionEnum, mimeType, label string) *models.SceneStreamEndpoint { type SceneStreamEndpoint struct {
return &models.SceneStreamEndpoint{ URL string `json:"url"`
MimeType *string `json:"mime_type"`
Label *string `json:"label"`
}
func makeStreamEndpoint(streamURL string, streamingResolution models.StreamingResolutionEnum, mimeType, label string) *SceneStreamEndpoint {
return &SceneStreamEndpoint{
URL: fmt.Sprintf("%s?resolution=%s", streamURL, streamingResolution.String()), URL: fmt.Sprintf("%s?resolution=%s", streamURL, streamingResolution.String()),
MimeType: &mimeType, MimeType: &mimeType,
Label: &label, Label: &label,
} }
} }
func GetSceneStreamPaths(scene *models.Scene, directStreamURL string, maxStreamingTranscodeSize models.StreamingResolutionEnum) ([]*models.SceneStreamEndpoint, error) { func GetSceneStreamPaths(scene *models.Scene, directStreamURL string, maxStreamingTranscodeSize models.StreamingResolutionEnum) ([]*SceneStreamEndpoint, error) {
if scene == nil { if scene == nil {
return nil, fmt.Errorf("nil scene") return nil, fmt.Errorf("nil scene")
} }
var ret []*models.SceneStreamEndpoint var ret []*SceneStreamEndpoint
mimeWebm := ffmpeg.MimeWebm mimeWebm := ffmpeg.MimeWebm
mimeHLS := ffmpeg.MimeHLS mimeHLS := ffmpeg.MimeHLS
mimeMp4 := ffmpeg.MimeMp4 mimeMp4 := ffmpeg.MimeMp4
@ -82,7 +88,7 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL string, maxStreami
if HasTranscode(scene, config.GetInstance().GetVideoFileNamingAlgorithm()) || ffmpeg.IsValidAudioForContainer(audioCodec, container) { if HasTranscode(scene, config.GetInstance().GetVideoFileNamingAlgorithm()) || ffmpeg.IsValidAudioForContainer(audioCodec, container) {
label := "Direct stream" label := "Direct stream"
ret = append(ret, &models.SceneStreamEndpoint{ ret = append(ret, &SceneStreamEndpoint{
URL: directStreamURL, URL: directStreamURL,
MimeType: &mimeMp4, MimeType: &mimeMp4,
Label: &label, Label: &label,
@ -92,7 +98,7 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL string, maxStreami
// only add mkv stream endpoint if the scene container is an mkv already // only add mkv stream endpoint if the scene container is an mkv already
if container == ffmpeg.Matroska { if container == ffmpeg.Matroska {
label := "mkv" label := "mkv"
ret = append(ret, &models.SceneStreamEndpoint{ ret = append(ret, &SceneStreamEndpoint{
URL: directStreamURL + ".mkv", URL: directStreamURL + ".mkv",
// set mkv to mp4 to trick the client, since many clients won't try mkv // set mkv to mp4 to trick the client, since many clients won't try mkv
MimeType: &mimeMp4, MimeType: &mimeMp4,
@ -115,8 +121,8 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL string, maxStreami
mp4LabelStandard := "MP4 Standard (480p)" // "STANDARD" mp4LabelStandard := "MP4 Standard (480p)" // "STANDARD"
mp4LabelLow := "MP4 Low (240p)" // "LOW" mp4LabelLow := "MP4 Low (240p)" // "LOW"
var webmStreams []*models.SceneStreamEndpoint var webmStreams []*SceneStreamEndpoint
var mp4Streams []*models.SceneStreamEndpoint var mp4Streams []*SceneStreamEndpoint
webmURL := directStreamURL + ".webm" webmURL := directStreamURL + ".webm"
mp4URL := directStreamURL + ".mp4" mp4URL := directStreamURL + ".mp4"
@ -149,7 +155,7 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL string, maxStreami
ret = append(ret, webmStreams...) ret = append(ret, webmStreams...)
ret = append(ret, mp4Streams...) ret = append(ret, mp4Streams...)
defaultStreams := []*models.SceneStreamEndpoint{ defaultStreams := []*SceneStreamEndpoint{
{ {
URL: directStreamURL + ".webm", URL: directStreamURL + ".webm",
MimeType: &mimeWebm, MimeType: &mimeWebm,
@ -159,7 +165,7 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL string, maxStreami
ret = append(ret, defaultStreams...) ret = append(ret, defaultStreams...)
hls := models.SceneStreamEndpoint{ hls := SceneStreamEndpoint{
URL: directStreamURL + ".m3u8", URL: directStreamURL + ".m3u8",
MimeType: &mimeHLS, MimeType: &mimeHLS,
Label: &labelHLS, Label: &labelHLS,

View file

@ -20,7 +20,7 @@ import (
type autoTagJob struct { type autoTagJob struct {
txnManager models.TransactionManager txnManager models.TransactionManager
input models.AutoTagMetadataInput input AutoTagMetadataInput
cache match.Cache cache match.Cache
} }
@ -40,7 +40,7 @@ func (j *autoTagJob) Execute(ctx context.Context, progress *job.Progress) {
logger.Infof("Finished autotag after %s", time.Since(begin).String()) logger.Infof("Finished autotag after %s", time.Since(begin).String())
} }
func (j *autoTagJob) isFileBasedAutoTag(input models.AutoTagMetadataInput) bool { func (j *autoTagJob) isFileBasedAutoTag(input AutoTagMetadataInput) bool {
const wildcard = "*" const wildcard = "*"
performerIds := input.Performers performerIds := input.Performers
studioIds := input.Studios studioIds := input.Studios

View file

@ -19,7 +19,7 @@ import (
type cleanJob struct { type cleanJob struct {
txnManager models.TransactionManager txnManager models.TransactionManager
input models.CleanMetadataInput input CleanMetadataInput
scanSubs *subscriptionManager scanSubs *subscriptionManager
} }
@ -488,7 +488,7 @@ func (j *cleanJob) deleteImage(ctx context.Context, imageID int) {
}, nil) }, nil)
} }
func getStashFromPath(pathToCheck string) *models.StashConfig { func getStashFromPath(pathToCheck string) *config.StashConfig {
for _, s := range config.GetInstance().GetStashPaths() { for _, s := range config.GetInstance().GetStashPaths() {
if fsutil.IsPathInDir(s.Path, filepath.Dir(pathToCheck)) { if fsutil.IsPathInDir(s.Path, filepath.Dir(pathToCheck)) {
return s return s
@ -497,7 +497,7 @@ func getStashFromPath(pathToCheck string) *models.StashConfig {
return nil return nil
} }
func getStashFromDirPath(pathToCheck string) *models.StashConfig { func getStashFromDirPath(pathToCheck string) *config.StashConfig {
for _, s := range config.GetInstance().GetStashPaths() { for _, s := range config.GetInstance().GetStashPaths() {
if fsutil.IsPathInDir(s.Path, pathToCheck) { if fsutil.IsPathInDir(s.Path, pathToCheck) {
return s return s

View file

@ -54,12 +54,28 @@ type ExportTask struct {
DownloadHash string DownloadHash string
} }
type ExportObjectTypeInput struct {
Ids []string `json:"ids"`
All *bool `json:"all"`
}
type ExportObjectsInput struct {
Scenes *ExportObjectTypeInput `json:"scenes"`
Images *ExportObjectTypeInput `json:"images"`
Studios *ExportObjectTypeInput `json:"studios"`
Performers *ExportObjectTypeInput `json:"performers"`
Tags *ExportObjectTypeInput `json:"tags"`
Movies *ExportObjectTypeInput `json:"movies"`
Galleries *ExportObjectTypeInput `json:"galleries"`
IncludeDependencies *bool `json:"includeDependencies"`
}
type exportSpec struct { type exportSpec struct {
IDs []int IDs []int
all bool all bool
} }
func newExportSpec(input *models.ExportObjectTypeInput) *exportSpec { func newExportSpec(input *ExportObjectTypeInput) *exportSpec {
if input == nil { if input == nil {
return &exportSpec{} return &exportSpec{}
} }
@ -77,7 +93,7 @@ func newExportSpec(input *models.ExportObjectTypeInput) *exportSpec {
return ret return ret
} }
func CreateExportTask(a models.HashAlgorithm, input models.ExportObjectsInput) *ExportTask { func CreateExportTask(a models.HashAlgorithm, input ExportObjectsInput) *ExportTask {
includeDeps := false includeDeps := false
if input.IncludeDependencies != nil { if input.IncludeDependencies != nil {
includeDeps = *input.IncludeDependencies includeDeps = *input.IncludeDependencies

View file

@ -17,11 +17,45 @@ import (
"github.com/stashapp/stash/pkg/utils" "github.com/stashapp/stash/pkg/utils"
) )
type GenerateMetadataInput struct {
Sprites *bool `json:"sprites"`
Previews *bool `json:"previews"`
ImagePreviews *bool `json:"imagePreviews"`
PreviewOptions *GeneratePreviewOptionsInput `json:"previewOptions"`
Markers *bool `json:"markers"`
MarkerImagePreviews *bool `json:"markerImagePreviews"`
MarkerScreenshots *bool `json:"markerScreenshots"`
Transcodes *bool `json:"transcodes"`
// Generate transcodes even if not required
ForceTranscodes *bool `json:"forceTranscodes"`
Phashes *bool `json:"phashes"`
InteractiveHeatmapsSpeeds *bool `json:"interactiveHeatmapsSpeeds"`
// scene ids to generate for
SceneIDs []string `json:"sceneIDs"`
// marker ids to generate for
MarkerIDs []string `json:"markerIDs"`
// overwrite existing media
Overwrite *bool `json:"overwrite"`
}
type GeneratePreviewOptionsInput struct {
// Number of segments in a preview file
PreviewSegments *int `json:"previewSegments"`
// Preview segment duration, in seconds
PreviewSegmentDuration *float64 `json:"previewSegmentDuration"`
// Duration of start of video to exclude when generating previews
PreviewExcludeStart *string `json:"previewExcludeStart"`
// Duration of end of video to exclude when generating previews
PreviewExcludeEnd *string `json:"previewExcludeEnd"`
// Preset when generating preview
PreviewPreset *models.PreviewPreset `json:"previewPreset"`
}
const generateQueueSize = 200000 const generateQueueSize = 200000
type GenerateJob struct { type GenerateJob struct {
txnManager models.TransactionManager txnManager models.TransactionManager
input models.GenerateMetadataInput input GenerateMetadataInput
overwrite bool overwrite bool
fileNamingAlgo models.HashAlgorithm fileNamingAlgo models.HashAlgorithm
@ -194,7 +228,7 @@ func (j *GenerateJob) queueTasks(ctx context.Context, g *generate.Generator, que
return totals return totals
} }
func getGeneratePreviewOptions(optionsInput models.GeneratePreviewOptionsInput) generate.PreviewOptions { func getGeneratePreviewOptions(optionsInput GeneratePreviewOptionsInput) generate.PreviewOptions {
config := config.GetInstance() config := config.GetInstance()
ret := generate.PreviewOptions{ ret := generate.PreviewOptions{
@ -246,7 +280,7 @@ func (j *GenerateJob) queueSceneJobs(ctx context.Context, g *generate.Generator,
generatePreviewOptions := j.input.PreviewOptions generatePreviewOptions := j.input.PreviewOptions
if generatePreviewOptions == nil { if generatePreviewOptions == nil {
generatePreviewOptions = &models.GeneratePreviewOptionsInput{} generatePreviewOptions = &GeneratePreviewOptionsInput{}
} }
options := getGeneratePreviewOptions(*generatePreviewOptions) options := getGeneratePreviewOptions(*generatePreviewOptions)

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"github.com/stashapp/stash/internal/identify" "github.com/stashapp/stash/internal/identify"
"github.com/stashapp/stash/pkg/job" "github.com/stashapp/stash/pkg/job"
@ -20,13 +21,13 @@ var ErrInput = errors.New("invalid request input")
type IdentifyJob struct { type IdentifyJob struct {
txnManager models.TransactionManager txnManager models.TransactionManager
postHookExecutor identify.SceneUpdatePostHookExecutor postHookExecutor identify.SceneUpdatePostHookExecutor
input models.IdentifyMetadataInput input identify.Options
stashBoxes models.StashBoxes stashBoxes []*models.StashBox
progress *job.Progress progress *job.Progress
} }
func CreateIdentifyJob(input models.IdentifyMetadataInput) *IdentifyJob { func CreateIdentifyJob(input identify.Options) *IdentifyJob {
return &IdentifyJob{ return &IdentifyJob{
txnManager: instance.TxnManager, txnManager: instance.TxnManager,
postHookExecutor: instance.PluginCache, postHookExecutor: instance.PluginCache,
@ -192,7 +193,7 @@ func (j *IdentifyJob) getSources() ([]identify.ScraperSource, error) {
return ret, nil return ret, nil
} }
func (j *IdentifyJob) getStashBox(src *models.ScraperSourceInput) (*models.StashBox, error) { func (j *IdentifyJob) getStashBox(src *scraper.Source) (*models.StashBox, error) {
if src.ScraperID != nil { if src.ScraperID != nil {
return nil, nil return nil, nil
} }
@ -202,7 +203,38 @@ func (j *IdentifyJob) getStashBox(src *models.ScraperSourceInput) (*models.Stash
return nil, fmt.Errorf("%w: stash_box_index or stash_box_endpoint or scraper_id must be set", ErrInput) return nil, fmt.Errorf("%w: stash_box_index or stash_box_endpoint or scraper_id must be set", ErrInput)
} }
return j.stashBoxes.ResolveStashBox(*src) return resolveStashBox(j.stashBoxes, *src)
}
func resolveStashBox(sb []*models.StashBox, source scraper.Source) (*models.StashBox, error) {
if source.StashBoxIndex != nil {
index := source.StashBoxIndex
if *index < 0 || *index >= len(sb) {
return nil, fmt.Errorf("%w: invalid stash_box_index: %d", models.ErrScraperSource, index)
}
return sb[*index], nil
}
if source.StashBoxEndpoint != nil {
var ret *models.StashBox
endpoint := *source.StashBoxEndpoint
for _, b := range sb {
if strings.EqualFold(endpoint, b.Endpoint) {
ret = b
}
}
if ret == nil {
return nil, fmt.Errorf(`%w: stash-box with endpoint "%s"`, models.ErrNotFound, endpoint)
}
return ret, nil
}
// neither stash-box inputs were provided, so assume it is a scraper
return nil, nil
} }
type stashboxSource struct { type stashboxSource struct {
@ -210,7 +242,7 @@ type stashboxSource struct {
endpoint string endpoint string
} }
func (s stashboxSource) ScrapeScene(ctx context.Context, sceneID int) (*models.ScrapedScene, error) { func (s stashboxSource) ScrapeScene(ctx context.Context, sceneID int) (*scraper.ScrapedScene, error) {
results, err := s.FindStashBoxSceneByFingerprints(ctx, sceneID) results, err := s.FindStashBoxSceneByFingerprints(ctx, sceneID)
if err != nil { if err != nil {
return nil, fmt.Errorf("error querying stash-box using scene ID %d: %w", sceneID, err) return nil, fmt.Errorf("error querying stash-box using scene ID %d: %w", sceneID, err)
@ -232,8 +264,8 @@ type scraperSource struct {
scraperID string scraperID string
} }
func (s scraperSource) ScrapeScene(ctx context.Context, sceneID int) (*models.ScrapedScene, error) { func (s scraperSource) ScrapeScene(ctx context.Context, sceneID int) (*scraper.ScrapedScene, error) {
content, err := s.cache.ScrapeID(ctx, s.scraperID, sceneID, models.ScrapeContentTypeScene) content, err := s.cache.ScrapeID(ctx, s.scraperID, sceneID, scraper.ScrapeContentTypeScene)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -243,7 +275,7 @@ func (s scraperSource) ScrapeScene(ctx context.Context, sceneID int) (*models.Sc
return nil, nil return nil, nil
} }
if scene, ok := content.(models.ScrapedScene); ok { if scene, ok := content.(scraper.ScrapedScene); ok {
return &scene, nil return &scene, nil
} }

View file

@ -11,6 +11,7 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/99designs/gqlgen/graphql"
"github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/internal/manager/config"
"github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/database"
"github.com/stashapp/stash/pkg/fsutil" "github.com/stashapp/stash/pkg/fsutil"
@ -35,7 +36,7 @@ type ImportTask struct {
BaseDir string BaseDir string
TmpZip string TmpZip string
Reset bool Reset bool
DuplicateBehaviour models.ImportDuplicateEnum DuplicateBehaviour ImportDuplicateEnum
MissingRefBehaviour models.ImportMissingRefEnum MissingRefBehaviour models.ImportMissingRefEnum
mappings *jsonschema.Mappings mappings *jsonschema.Mappings
@ -43,7 +44,13 @@ type ImportTask struct {
fileNamingAlgorithm models.HashAlgorithm fileNamingAlgorithm models.HashAlgorithm
} }
func CreateImportTask(a models.HashAlgorithm, input models.ImportObjectsInput) (*ImportTask, error) { type ImportObjectsInput struct {
File graphql.Upload `json:"file"`
DuplicateBehaviour ImportDuplicateEnum `json:"duplicateBehaviour"`
MissingRefBehaviour models.ImportMissingRefEnum `json:"missingRefBehaviour"`
}
func CreateImportTask(a models.HashAlgorithm, input ImportObjectsInput) (*ImportTask, error) {
baseDir, err := instance.Paths.Generated.TempDir("import") baseDir, err := instance.Paths.Generated.TempDir("import")
if err != nil { if err != nil {
logger.Errorf("error creating temporary directory for import: %s", err.Error()) logger.Errorf("error creating temporary directory for import: %s", err.Error())
@ -101,7 +108,7 @@ func (t *ImportTask) Start(ctx context.Context) {
// set default behaviour if not provided // set default behaviour if not provided
if !t.DuplicateBehaviour.IsValid() { if !t.DuplicateBehaviour.IsValid() {
t.DuplicateBehaviour = models.ImportDuplicateEnumFail t.DuplicateBehaviour = ImportDuplicateEnumFail
} }
if !t.MissingRefBehaviour.IsValid() { if !t.MissingRefBehaviour.IsValid() {
t.MissingRefBehaviour = models.ImportMissingRefEnumFail t.MissingRefBehaviour = models.ImportMissingRefEnumFail

View file

@ -6,10 +6,10 @@ import (
"github.com/stashapp/stash/pkg/job" "github.com/stashapp/stash/pkg/job"
"github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/plugin"
) )
func (s *Manager) RunPluginTask(ctx context.Context, pluginID string, taskName string, args []*models.PluginArgInput) int { func (s *Manager) RunPluginTask(ctx context.Context, pluginID string, taskName string, args []*plugin.PluginArgInput) int {
j := job.MakeJobExec(func(jobCtx context.Context, progress *job.Progress) { j := job.MakeJobExec(func(jobCtx context.Context, progress *job.Progress) {
pluginProgress := make(chan float64) pluginProgress := make(chan float64)
task, err := s.PluginCache.CreateTask(ctx, pluginID, taskName, args, pluginProgress) task, err := s.PluginCache.CreateTask(ctx, pluginID, taskName, args, pluginProgress)

View file

@ -25,7 +25,7 @@ const scanQueueSize = 200000
type ScanJob struct { type ScanJob struct {
txnManager models.TransactionManager txnManager models.TransactionManager
input models.ScanMetadataInput input ScanMetadataInput
subscriptions *subscriptionManager subscriptions *subscriptionManager
} }
@ -88,15 +88,15 @@ func (j *ScanJob) Execute(ctx context.Context, progress *job.Progress) {
task := ScanTask{ task := ScanTask{
TxnManager: j.txnManager, TxnManager: j.txnManager,
file: file.FSFile(f.path, f.info), file: file.FSFile(f.path, f.info),
UseFileMetadata: utils.IsTrue(input.UseFileMetadata), UseFileMetadata: input.UseFileMetadata,
StripFileExtension: utils.IsTrue(input.StripFileExtension), StripFileExtension: input.StripFileExtension,
fileNamingAlgorithm: fileNamingAlgo, fileNamingAlgorithm: fileNamingAlgo,
calculateMD5: calculateMD5, calculateMD5: calculateMD5,
GeneratePreview: utils.IsTrue(input.ScanGeneratePreviews), GeneratePreview: input.ScanGeneratePreviews,
GenerateImagePreview: utils.IsTrue(input.ScanGenerateImagePreviews), GenerateImagePreview: input.ScanGenerateImagePreviews,
GenerateSprite: utils.IsTrue(input.ScanGenerateSprites), GenerateSprite: input.ScanGenerateSprites,
GeneratePhash: utils.IsTrue(input.ScanGeneratePhashes), GeneratePhash: input.ScanGeneratePhashes,
GenerateThumbnails: utils.IsTrue(input.ScanGenerateThumbnails), GenerateThumbnails: input.ScanGenerateThumbnails,
progress: progress, progress: progress,
CaseSensitiveFs: f.caseSensitiveFs, CaseSensitiveFs: f.caseSensitiveFs,
mutexManager: mutexManager, mutexManager: mutexManager,
@ -145,7 +145,7 @@ func (j *ScanJob) Execute(ctx context.Context, progress *job.Progress) {
j.subscriptions.notify() j.subscriptions.notify()
} }
func (j *ScanJob) queueFiles(ctx context.Context, paths []*models.StashConfig, scanQueue chan<- scanFile, parallelTasks int) (total int, newFiles int) { func (j *ScanJob) queueFiles(ctx context.Context, paths []*config.StashConfig, scanQueue chan<- scanFile, parallelTasks int) (total int, newFiles int) {
defer close(scanQueue) defer close(scanQueue)
var minModTime time.Time var minModTime time.Time
@ -322,7 +322,7 @@ func (t *ScanTask) Start(ctx context.Context) {
iwg.Add() iwg.Add()
go t.progress.ExecuteTask(fmt.Sprintf("Generating preview for %s", path), func() { go t.progress.ExecuteTask(fmt.Sprintf("Generating preview for %s", path), func() {
options := getGeneratePreviewOptions(models.GeneratePreviewOptionsInput{}) options := getGeneratePreviewOptions(GeneratePreviewOptionsInput{})
const overwrite = false const overwrite = false
g := &generate.Generator{ g := &generate.Generator{
@ -349,7 +349,7 @@ func (t *ScanTask) Start(ctx context.Context) {
iwg.Wait() iwg.Wait()
} }
func walkFilesToScan(s *models.StashConfig, f filepath.WalkFunc) error { func walkFilesToScan(s *config.StashConfig, f filepath.WalkFunc) error {
config := config.GetInstance() config := config.GetInstance()
vidExt := config.GetVideoExtensions() vidExt := config.GetVideoExtensions()
imgExt := config.GetImageExtensions() imgExt := config.GetImageExtensions()

View file

@ -1,46 +0,0 @@
package models
type ResolutionRange struct {
min, max int
}
var resolutionRanges = map[ResolutionEnum]ResolutionRange{
ResolutionEnumVeryLow: {144, 239},
ResolutionEnumLow: {240, 359},
ResolutionEnumR360p: {360, 479},
ResolutionEnumStandard: {480, 539},
ResolutionEnumWebHd: {540, 719},
ResolutionEnumStandardHd: {720, 1079},
ResolutionEnumFullHd: {1080, 1439},
ResolutionEnumQuadHd: {1440, 1919},
ResolutionEnumVrHd: {1920, 2159},
ResolutionEnumFourK: {2160, 2879},
ResolutionEnumFiveK: {2880, 3383},
ResolutionEnumSixK: {3384, 4319},
ResolutionEnumEightK: {4320, 8639},
}
// GetMaxResolution returns the maximum width or height that media must be
// to qualify as this resolution.
func (r *ResolutionEnum) GetMaxResolution() int {
return resolutionRanges[*r].max
}
// GetMinResolution returns the minimum width or height that media must be
// to qualify as this resolution.
func (r ResolutionEnum) GetMinResolution() int {
return resolutionRanges[r].min
}
var streamingResolutionMax = map[StreamingResolutionEnum]int{
StreamingResolutionEnumLow: resolutionRanges[ResolutionEnumLow].min,
StreamingResolutionEnumStandard: resolutionRanges[ResolutionEnumStandard].min,
StreamingResolutionEnumStandardHd: resolutionRanges[ResolutionEnumStandardHd].min,
StreamingResolutionEnumFullHd: resolutionRanges[ResolutionEnumFullHd].min,
StreamingResolutionEnumFourK: resolutionRanges[ResolutionEnumFourK].min,
StreamingResolutionEnumOriginal: 0,
}
func (r StreamingResolutionEnum) GetMaxResolution() int {
return streamingResolutionMax[r]
}

108
pkg/models/filter.go Normal file
View file

@ -0,0 +1,108 @@
package models
import (
"fmt"
"io"
"strconv"
)
type CriterionModifier string
const (
// =
CriterionModifierEquals CriterionModifier = "EQUALS"
// !=
CriterionModifierNotEquals CriterionModifier = "NOT_EQUALS"
// >
CriterionModifierGreaterThan CriterionModifier = "GREATER_THAN"
// <
CriterionModifierLessThan CriterionModifier = "LESS_THAN"
// IS NULL
CriterionModifierIsNull CriterionModifier = "IS_NULL"
// IS NOT NULL
CriterionModifierNotNull CriterionModifier = "NOT_NULL"
// INCLUDES ALL
CriterionModifierIncludesAll CriterionModifier = "INCLUDES_ALL"
CriterionModifierIncludes CriterionModifier = "INCLUDES"
CriterionModifierExcludes CriterionModifier = "EXCLUDES"
// MATCHES REGEX
CriterionModifierMatchesRegex CriterionModifier = "MATCHES_REGEX"
// NOT MATCHES REGEX
CriterionModifierNotMatchesRegex CriterionModifier = "NOT_MATCHES_REGEX"
// >= AND <=
CriterionModifierBetween CriterionModifier = "BETWEEN"
// < OR >
CriterionModifierNotBetween CriterionModifier = "NOT_BETWEEN"
)
var AllCriterionModifier = []CriterionModifier{
CriterionModifierEquals,
CriterionModifierNotEquals,
CriterionModifierGreaterThan,
CriterionModifierLessThan,
CriterionModifierIsNull,
CriterionModifierNotNull,
CriterionModifierIncludesAll,
CriterionModifierIncludes,
CriterionModifierExcludes,
CriterionModifierMatchesRegex,
CriterionModifierNotMatchesRegex,
CriterionModifierBetween,
CriterionModifierNotBetween,
}
func (e CriterionModifier) IsValid() bool {
switch e {
case CriterionModifierEquals, CriterionModifierNotEquals, CriterionModifierGreaterThan, CriterionModifierLessThan, CriterionModifierIsNull, CriterionModifierNotNull, CriterionModifierIncludesAll, CriterionModifierIncludes, CriterionModifierExcludes, CriterionModifierMatchesRegex, CriterionModifierNotMatchesRegex, CriterionModifierBetween, CriterionModifierNotBetween:
return true
}
return false
}
func (e CriterionModifier) String() string {
return string(e)
}
func (e *CriterionModifier) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = CriterionModifier(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid CriterionModifier", str)
}
return nil
}
func (e CriterionModifier) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type StringCriterionInput struct {
Value string `json:"value"`
Modifier CriterionModifier `json:"modifier"`
}
type IntCriterionInput struct {
Value int `json:"value"`
Value2 *int `json:"value2"`
Modifier CriterionModifier `json:"modifier"`
}
type ResolutionCriterionInput struct {
Value ResolutionEnum `json:"value"`
Modifier CriterionModifier `json:"modifier"`
}
type HierarchicalMultiCriterionInput struct {
Value []string `json:"value"`
Modifier CriterionModifier `json:"modifier"`
Depth *int `json:"depth"`
}
type MultiCriterionInput struct {
Value []string `json:"value"`
Modifier CriterionModifier `json:"modifier"`
}

View file

@ -1,9 +1,65 @@
package models package models
import (
"fmt"
"io"
"strconv"
)
// PerPageAll is the value used for perPage to indicate all results should be // PerPageAll is the value used for perPage to indicate all results should be
// returned. // returned.
const PerPageAll = -1 const PerPageAll = -1
type SortDirectionEnum string
const (
SortDirectionEnumAsc SortDirectionEnum = "ASC"
SortDirectionEnumDesc SortDirectionEnum = "DESC"
)
var AllSortDirectionEnum = []SortDirectionEnum{
SortDirectionEnumAsc,
SortDirectionEnumDesc,
}
func (e SortDirectionEnum) IsValid() bool {
switch e {
case SortDirectionEnumAsc, SortDirectionEnumDesc:
return true
}
return false
}
func (e SortDirectionEnum) String() string {
return string(e)
}
func (e *SortDirectionEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = SortDirectionEnum(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid SortDirectionEnum", str)
}
return nil
}
func (e SortDirectionEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type FindFilterType struct {
Q *string `json:"q"`
Page *int `json:"page"`
// use per_page = -1 to indicate all results. Defaults to 25.
PerPage *int `json:"per_page"`
Sort *string `json:"sort"`
Direction *SortDirectionEnum `json:"direction"`
}
func (ff FindFilterType) GetSort(defaultSort string) string { func (ff FindFilterType) GetSort(defaultSort string) string {
var sort string var sort string
if ff.Sort == nil { if ff.Sort == nil {

View file

@ -1,5 +1,71 @@
package models package models
type GalleryFilterType struct {
And *GalleryFilterType `json:"AND"`
Or *GalleryFilterType `json:"OR"`
Not *GalleryFilterType `json:"NOT"`
Title *StringCriterionInput `json:"title"`
Details *StringCriterionInput `json:"details"`
// Filter by file checksum
Checksum *StringCriterionInput `json:"checksum"`
// Filter by path
Path *StringCriterionInput `json:"path"`
// Filter to only include galleries missing this property
IsMissing *string `json:"is_missing"`
// Filter to include/exclude galleries that were created from zip
IsZip *bool `json:"is_zip"`
// Filter by rating
Rating *IntCriterionInput `json:"rating"`
// Filter by organized
Organized *bool `json:"organized"`
// Filter by average image resolution
AverageResolution *ResolutionCriterionInput `json:"average_resolution"`
// Filter to only include galleries with this studio
Studios *HierarchicalMultiCriterionInput `json:"studios"`
// Filter to only include galleries with these tags
Tags *HierarchicalMultiCriterionInput `json:"tags"`
// Filter by tag count
TagCount *IntCriterionInput `json:"tag_count"`
// Filter to only include galleries with performers with these tags
PerformerTags *HierarchicalMultiCriterionInput `json:"performer_tags"`
// Filter to only include galleries with these performers
Performers *MultiCriterionInput `json:"performers"`
// Filter by performer count
PerformerCount *IntCriterionInput `json:"performer_count"`
// Filter galleries that have performers that have been favorited
PerformerFavorite *bool `json:"performer_favorite"`
// Filter galleries by performer age at time of gallery
PerformerAge *IntCriterionInput `json:"performer_age"`
// Filter by number of images in this gallery
ImageCount *IntCriterionInput `json:"image_count"`
// Filter by url
URL *StringCriterionInput `json:"url"`
}
type GalleryUpdateInput struct {
ClientMutationID *string `json:"clientMutationId"`
ID string `json:"id"`
Title *string `json:"title"`
URL *string `json:"url"`
Date *string `json:"date"`
Details *string `json:"details"`
Rating *int `json:"rating"`
Organized *bool `json:"organized"`
SceneIds []string `json:"scene_ids"`
StudioID *string `json:"studio_id"`
TagIds []string `json:"tag_ids"`
PerformerIds []string `json:"performer_ids"`
}
type GalleryDestroyInput struct {
Ids []string `json:"ids"`
// If true, then the zip file will be deleted if the gallery is zip-file-based.
// If gallery is folder-based, then any files not associated with other
// galleries will be deleted, along with the folder, if it is not empty.
DeleteFile *bool `json:"delete_file"`
DeleteGenerated *bool `json:"delete_generated"`
}
type GalleryReader interface { type GalleryReader interface {
Find(id int) (*Gallery, error) Find(id int) (*Gallery, error)
FindMany(ids []int) ([]*Gallery, error) FindMany(ids []int) ([]*Gallery, error)

91
pkg/models/generate.go Normal file
View file

@ -0,0 +1,91 @@
package models
import (
"fmt"
"io"
"strconv"
)
type GenerateMetadataOptions struct {
Sprites *bool `json:"sprites"`
Previews *bool `json:"previews"`
ImagePreviews *bool `json:"imagePreviews"`
PreviewOptions *GeneratePreviewOptions `json:"previewOptions"`
Markers *bool `json:"markers"`
MarkerImagePreviews *bool `json:"markerImagePreviews"`
MarkerScreenshots *bool `json:"markerScreenshots"`
Transcodes *bool `json:"transcodes"`
Phashes *bool `json:"phashes"`
InteractiveHeatmapsSpeeds *bool `json:"interactiveHeatmapsSpeeds"`
}
type GeneratePreviewOptions struct {
// Number of segments in a preview file
PreviewSegments *int `json:"previewSegments"`
// Preview segment duration, in seconds
PreviewSegmentDuration *float64 `json:"previewSegmentDuration"`
// Duration of start of video to exclude when generating previews
PreviewExcludeStart *string `json:"previewExcludeStart"`
// Duration of end of video to exclude when generating previews
PreviewExcludeEnd *string `json:"previewExcludeEnd"`
// Preset when generating preview
PreviewPreset *PreviewPreset `json:"previewPreset"`
}
type PreviewPreset string
const (
// X264_ULTRAFAST
PreviewPresetUltrafast PreviewPreset = "ultrafast"
// X264_VERYFAST
PreviewPresetVeryfast PreviewPreset = "veryfast"
// X264_FAST
PreviewPresetFast PreviewPreset = "fast"
// X264_MEDIUM
PreviewPresetMedium PreviewPreset = "medium"
// X264_SLOW
PreviewPresetSlow PreviewPreset = "slow"
// X264_SLOWER
PreviewPresetSlower PreviewPreset = "slower"
// X264_VERYSLOW
PreviewPresetVeryslow PreviewPreset = "veryslow"
)
var AllPreviewPreset = []PreviewPreset{
PreviewPresetUltrafast,
PreviewPresetVeryfast,
PreviewPresetFast,
PreviewPresetMedium,
PreviewPresetSlow,
PreviewPresetSlower,
PreviewPresetVeryslow,
}
func (e PreviewPreset) IsValid() bool {
switch e {
case PreviewPresetUltrafast, PreviewPresetVeryfast, PreviewPresetFast, PreviewPresetMedium, PreviewPresetSlow, PreviewPresetSlower, PreviewPresetVeryslow:
return true
}
return false
}
func (e PreviewPreset) String() string {
return string(e)
}
func (e *PreviewPreset) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = PreviewPreset(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid PreviewPreset", str)
}
return nil
}
func (e PreviewPreset) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}

View file

@ -1,5 +1,54 @@
package models package models
type ImageFilterType struct {
And *ImageFilterType `json:"AND"`
Or *ImageFilterType `json:"OR"`
Not *ImageFilterType `json:"NOT"`
Title *StringCriterionInput `json:"title"`
// Filter by file checksum
Checksum *StringCriterionInput `json:"checksum"`
// Filter by path
Path *StringCriterionInput `json:"path"`
// Filter by rating
Rating *IntCriterionInput `json:"rating"`
// Filter by organized
Organized *bool `json:"organized"`
// Filter by o-counter
OCounter *IntCriterionInput `json:"o_counter"`
// Filter by resolution
Resolution *ResolutionCriterionInput `json:"resolution"`
// Filter to only include images missing this property
IsMissing *string `json:"is_missing"`
// Filter to only include images with this studio
Studios *HierarchicalMultiCriterionInput `json:"studios"`
// Filter to only include images with these tags
Tags *HierarchicalMultiCriterionInput `json:"tags"`
// Filter by tag count
TagCount *IntCriterionInput `json:"tag_count"`
// Filter to only include images with performers with these tags
PerformerTags *HierarchicalMultiCriterionInput `json:"performer_tags"`
// Filter to only include images with these performers
Performers *MultiCriterionInput `json:"performers"`
// Filter by performer count
PerformerCount *IntCriterionInput `json:"performer_count"`
// Filter images that have performers that have been favorited
PerformerFavorite *bool `json:"performer_favorite"`
// Filter to only include images with these galleries
Galleries *MultiCriterionInput `json:"galleries"`
}
type ImageDestroyInput struct {
ID string `json:"id"`
DeleteFile *bool `json:"delete_file"`
DeleteGenerated *bool `json:"delete_generated"`
}
type ImagesDestroyInput struct {
Ids []string `json:"ids"`
DeleteFile *bool `json:"delete_file"`
DeleteGenerated *bool `json:"delete_generated"`
}
type ImageQueryOptions struct { type ImageQueryOptions struct {
QueryOptions QueryOptions
ImageFilter *ImageFilterType ImageFilter *ImageFilterType

50
pkg/models/import.go Normal file
View file

@ -0,0 +1,50 @@
package models
import (
"fmt"
"io"
"strconv"
)
type ImportMissingRefEnum string
const (
ImportMissingRefEnumIgnore ImportMissingRefEnum = "IGNORE"
ImportMissingRefEnumFail ImportMissingRefEnum = "FAIL"
ImportMissingRefEnumCreate ImportMissingRefEnum = "CREATE"
)
var AllImportMissingRefEnum = []ImportMissingRefEnum{
ImportMissingRefEnumIgnore,
ImportMissingRefEnumFail,
ImportMissingRefEnumCreate,
}
func (e ImportMissingRefEnum) IsValid() bool {
switch e {
case ImportMissingRefEnumIgnore, ImportMissingRefEnumFail, ImportMissingRefEnumCreate:
return true
}
return false
}
func (e ImportMissingRefEnum) String() string {
return string(e)
}
func (e *ImportMissingRefEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = ImportMissingRefEnum(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ImportMissingRefEnum", str)
}
return nil
}
func (e ImportMissingRefEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}

View file

@ -1,6 +1,53 @@
package models package models
import "time" import (
"fmt"
"io"
"strconv"
"time"
)
type HashAlgorithm string
const (
HashAlgorithmMd5 HashAlgorithm = "MD5"
// oshash
HashAlgorithmOshash HashAlgorithm = "OSHASH"
)
var AllHashAlgorithm = []HashAlgorithm{
HashAlgorithmMd5,
HashAlgorithmOshash,
}
func (e HashAlgorithm) IsValid() bool {
switch e {
case HashAlgorithmMd5, HashAlgorithmOshash:
return true
}
return false
}
func (e HashAlgorithm) String() string {
return string(e)
}
func (e *HashAlgorithm) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = HashAlgorithm(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid HashAlgorithm", str)
}
return nil
}
func (e HashAlgorithm) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type File struct { type File struct {
Checksum string `db:"checksum" json:"checksum"` Checksum string `db:"checksum" json:"checksum"`

View file

@ -1,5 +1,64 @@
package models package models
import (
"fmt"
"io"
"strconv"
)
type FilterMode string
const (
FilterModeScenes FilterMode = "SCENES"
FilterModePerformers FilterMode = "PERFORMERS"
FilterModeStudios FilterMode = "STUDIOS"
FilterModeGalleries FilterMode = "GALLERIES"
FilterModeSceneMarkers FilterMode = "SCENE_MARKERS"
FilterModeMovies FilterMode = "MOVIES"
FilterModeTags FilterMode = "TAGS"
FilterModeImages FilterMode = "IMAGES"
)
var AllFilterMode = []FilterMode{
FilterModeScenes,
FilterModePerformers,
FilterModeStudios,
FilterModeGalleries,
FilterModeSceneMarkers,
FilterModeMovies,
FilterModeTags,
FilterModeImages,
}
func (e FilterMode) IsValid() bool {
switch e {
case FilterModeScenes, FilterModePerformers, FilterModeStudios, FilterModeGalleries, FilterModeSceneMarkers, FilterModeMovies, FilterModeTags, FilterModeImages:
return true
}
return false
}
func (e FilterMode) String() string {
return string(e)
}
func (e *FilterMode) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = FilterMode(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid FilterMode", str)
}
return nil
}
func (e FilterMode) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type SavedFilter struct { type SavedFilter struct {
ID int `db:"id" json:"id"` ID int `db:"id" json:"id"`
Mode FilterMode `db:"mode" json:"mode"` Mode FilterMode `db:"mode" json:"mode"`

View file

@ -122,6 +122,30 @@ type ScenePartial struct {
InteractiveSpeed *sql.NullInt64 `db:"interactive_speed" json:"interactive_speed"` InteractiveSpeed *sql.NullInt64 `db:"interactive_speed" json:"interactive_speed"`
} }
type SceneMovieInput struct {
MovieID string `json:"movie_id"`
SceneIndex *int `json:"scene_index"`
}
type SceneUpdateInput struct {
ClientMutationID *string `json:"clientMutationId"`
ID string `json:"id"`
Title *string `json:"title"`
Details *string `json:"details"`
URL *string `json:"url"`
Date *string `json:"date"`
Rating *int `json:"rating"`
Organized *bool `json:"organized"`
StudioID *string `json:"studio_id"`
GalleryIds []string `json:"gallery_ids"`
PerformerIds []string `json:"performer_ids"`
Movies []*SceneMovieInput `json:"movies"`
TagIds []string `json:"tag_ids"`
// This should be a URL or a base64 encoded data URL
CoverImage *string `json:"cover_image"`
StashIds []*StashIDInput `json:"stash_ids"`
}
// UpdateInput constructs a SceneUpdateInput using the populated fields in the ScenePartial object. // UpdateInput constructs a SceneUpdateInput using the populated fields in the ScenePartial object.
func (s ScenePartial) UpdateInput() SceneUpdateInput { func (s ScenePartial) UpdateInput() SceneUpdateInput {
boolPtrCopy := func(v *bool) *bool { boolPtrCopy := func(v *bool) *bool {

View file

@ -4,6 +4,78 @@ import (
"database/sql" "database/sql"
) )
type ScrapedStudio struct {
// Set if studio matched
StoredID *string `json:"stored_id"`
Name string `json:"name"`
URL *string `json:"url"`
Image *string `json:"image"`
RemoteSiteID *string `json:"remote_site_id"`
}
func (ScrapedStudio) IsScrapedContent() {}
// A performer from a scraping operation...
type ScrapedPerformer struct {
// Set if performer matched
StoredID *string `json:"stored_id"`
Name *string `json:"name"`
Gender *string `json:"gender"`
URL *string `json:"url"`
Twitter *string `json:"twitter"`
Instagram *string `json:"instagram"`
Birthdate *string `json:"birthdate"`
Ethnicity *string `json:"ethnicity"`
Country *string `json:"country"`
EyeColor *string `json:"eye_color"`
Height *string `json:"height"`
Measurements *string `json:"measurements"`
FakeTits *string `json:"fake_tits"`
CareerLength *string `json:"career_length"`
Tattoos *string `json:"tattoos"`
Piercings *string `json:"piercings"`
Aliases *string `json:"aliases"`
Tags []*ScrapedTag `json:"tags"`
// This should be a base64 encoded data URL
Image *string `json:"image"`
Images []string `json:"images"`
Details *string `json:"details"`
DeathDate *string `json:"death_date"`
HairColor *string `json:"hair_color"`
Weight *string `json:"weight"`
RemoteSiteID *string `json:"remote_site_id"`
}
func (ScrapedPerformer) IsScrapedContent() {}
type ScrapedTag struct {
// Set if tag matched
StoredID *string `json:"stored_id"`
Name string `json:"name"`
}
func (ScrapedTag) IsScrapedContent() {}
// A movie from a scraping operation...
type ScrapedMovie struct {
StoredID *string `json:"stored_id"`
Name *string `json:"name"`
Aliases *string `json:"aliases"`
Duration *string `json:"duration"`
Date *string `json:"date"`
Rating *string `json:"rating"`
Director *string `json:"director"`
URL *string `json:"url"`
Synopsis *string `json:"synopsis"`
Studio *ScrapedStudio `json:"studio"`
// This should be a base64 encoded data URL
FrontImage *string `json:"front_image"`
// This should be a base64 encoded data URL
BackImage *string `json:"back_image"`
}
func (ScrapedMovie) IsScrapedContent() {}
type ScrapedItem struct { type ScrapedItem struct {
ID int `db:"id" json:"id"` ID int `db:"id" json:"id"`
Title sql.NullString `db:"title" json:"title"` Title sql.NullString `db:"title" json:"title"`

View file

@ -1,5 +1,23 @@
package models package models
type MovieFilterType struct {
Name *StringCriterionInput `json:"name"`
Director *StringCriterionInput `json:"director"`
Synopsis *StringCriterionInput `json:"synopsis"`
// Filter by duration (in seconds)
Duration *IntCriterionInput `json:"duration"`
// Filter by rating
Rating *IntCriterionInput `json:"rating"`
// Filter to only include movies with this studio
Studios *HierarchicalMultiCriterionInput `json:"studios"`
// Filter to only include movies missing this property
IsMissing *string `json:"is_missing"`
// Filter by url
URL *StringCriterionInput `json:"url"`
// Filter to only include movies where performer appears in a scene
Performers *MultiCriterionInput `json:"performers"`
}
type MovieReader interface { type MovieReader interface {
Find(id int) (*Movie, error) Find(id int) (*Movie, error)
FindMany(ids []int) ([]*Movie, error) FindMany(ids []int) ([]*Movie, error)

View file

@ -1,5 +1,129 @@
package models package models
import (
"fmt"
"io"
"strconv"
)
type GenderEnum string
const (
GenderEnumMale GenderEnum = "MALE"
GenderEnumFemale GenderEnum = "FEMALE"
GenderEnumTransgenderMale GenderEnum = "TRANSGENDER_MALE"
GenderEnumTransgenderFemale GenderEnum = "TRANSGENDER_FEMALE"
GenderEnumIntersex GenderEnum = "INTERSEX"
GenderEnumNonBinary GenderEnum = "NON_BINARY"
)
var AllGenderEnum = []GenderEnum{
GenderEnumMale,
GenderEnumFemale,
GenderEnumTransgenderMale,
GenderEnumTransgenderFemale,
GenderEnumIntersex,
GenderEnumNonBinary,
}
func (e GenderEnum) IsValid() bool {
switch e {
case GenderEnumMale, GenderEnumFemale, GenderEnumTransgenderMale, GenderEnumTransgenderFemale, GenderEnumIntersex, GenderEnumNonBinary:
return true
}
return false
}
func (e GenderEnum) String() string {
return string(e)
}
func (e *GenderEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = GenderEnum(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid GenderEnum", str)
}
return nil
}
func (e GenderEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
type GenderCriterionInput struct {
Value *GenderEnum `json:"value"`
Modifier CriterionModifier `json:"modifier"`
}
type PerformerFilterType struct {
And *PerformerFilterType `json:"AND"`
Or *PerformerFilterType `json:"OR"`
Not *PerformerFilterType `json:"NOT"`
Name *StringCriterionInput `json:"name"`
Details *StringCriterionInput `json:"details"`
// Filter by favorite
FilterFavorites *bool `json:"filter_favorites"`
// Filter by birth year
BirthYear *IntCriterionInput `json:"birth_year"`
// Filter by age
Age *IntCriterionInput `json:"age"`
// Filter by ethnicity
Ethnicity *StringCriterionInput `json:"ethnicity"`
// Filter by country
Country *StringCriterionInput `json:"country"`
// Filter by eye color
EyeColor *StringCriterionInput `json:"eye_color"`
// Filter by height
Height *StringCriterionInput `json:"height"`
// Filter by measurements
Measurements *StringCriterionInput `json:"measurements"`
// Filter by fake tits value
FakeTits *StringCriterionInput `json:"fake_tits"`
// Filter by career length
CareerLength *StringCriterionInput `json:"career_length"`
// Filter by tattoos
Tattoos *StringCriterionInput `json:"tattoos"`
// Filter by piercings
Piercings *StringCriterionInput `json:"piercings"`
// Filter by aliases
Aliases *StringCriterionInput `json:"aliases"`
// Filter by gender
Gender *GenderCriterionInput `json:"gender"`
// Filter to only include performers missing this property
IsMissing *string `json:"is_missing"`
// Filter to only include performers with these tags
Tags *HierarchicalMultiCriterionInput `json:"tags"`
// Filter by tag count
TagCount *IntCriterionInput `json:"tag_count"`
// Filter by scene count
SceneCount *IntCriterionInput `json:"scene_count"`
// Filter by image count
ImageCount *IntCriterionInput `json:"image_count"`
// Filter by gallery count
GalleryCount *IntCriterionInput `json:"gallery_count"`
// Filter by StashID
StashID *StringCriterionInput `json:"stash_id"`
// Filter by rating
Rating *IntCriterionInput `json:"rating"`
// Filter by url
URL *StringCriterionInput `json:"url"`
// Filter by hair color
HairColor *StringCriterionInput `json:"hair_color"`
// Filter by weight
Weight *IntCriterionInput `json:"weight"`
// Filter by death year
DeathYear *IntCriterionInput `json:"death_year"`
// Filter by studios where performer appears in scene/image/gallery
Studios *HierarchicalMultiCriterionInput `json:"studios"`
// Filter by autotag ignore value
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
}
type PerformerReader interface { type PerformerReader interface {
Find(id int) (*Performer, error) Find(id int) (*Performer, error)
FindMany(ids []int) ([]*Performer, error) FindMany(ids []int) ([]*Performer, error)

183
pkg/models/resolution.go Normal file
View file

@ -0,0 +1,183 @@
package models
import (
"fmt"
"io"
"strconv"
)
type ResolutionRange struct {
min, max int
}
var resolutionRanges = map[ResolutionEnum]ResolutionRange{
ResolutionEnum("VERY_LOW"): {144, 239},
ResolutionEnum("LOW"): {240, 359},
ResolutionEnum("R360P"): {360, 479},
ResolutionEnum("STANDARD"): {480, 539},
ResolutionEnum("WEB_HD"): {540, 719},
ResolutionEnum("STANDARD_HD"): {720, 1079},
ResolutionEnum("FULL_HD"): {1080, 1439},
ResolutionEnum("QUAD_HD"): {1440, 1919},
ResolutionEnum("VR_HD"): {1920, 2159},
ResolutionEnum("FOUR_K"): {2160, 2879},
ResolutionEnum("FIVE_K"): {2880, 3383},
ResolutionEnum("SIX_K"): {3384, 4319},
ResolutionEnum("EIGHT_K"): {4320, 8639},
}
type ResolutionEnum string
const (
// 144p
ResolutionEnumVeryLow ResolutionEnum = "VERY_LOW"
// 240p
ResolutionEnumLow ResolutionEnum = "LOW"
// 360p
ResolutionEnumR360p ResolutionEnum = "R360P"
// 480p
ResolutionEnumStandard ResolutionEnum = "STANDARD"
// 540p
ResolutionEnumWebHd ResolutionEnum = "WEB_HD"
// 720p
ResolutionEnumStandardHd ResolutionEnum = "STANDARD_HD"
// 1080p
ResolutionEnumFullHd ResolutionEnum = "FULL_HD"
// 1440p
ResolutionEnumQuadHd ResolutionEnum = "QUAD_HD"
// 1920p
ResolutionEnumVrHd ResolutionEnum = "VR_HD"
// 4k
ResolutionEnumFourK ResolutionEnum = "FOUR_K"
// 5k
ResolutionEnumFiveK ResolutionEnum = "FIVE_K"
// 6k
ResolutionEnumSixK ResolutionEnum = "SIX_K"
// 8k
ResolutionEnumEightK ResolutionEnum = "EIGHT_K"
)
var AllResolutionEnum = []ResolutionEnum{
ResolutionEnumVeryLow,
ResolutionEnumLow,
ResolutionEnumR360p,
ResolutionEnumStandard,
ResolutionEnumWebHd,
ResolutionEnumStandardHd,
ResolutionEnumFullHd,
ResolutionEnumQuadHd,
ResolutionEnumVrHd,
ResolutionEnumFourK,
ResolutionEnumFiveK,
ResolutionEnumSixK,
ResolutionEnumEightK,
}
func (e ResolutionEnum) IsValid() bool {
switch e {
case ResolutionEnumVeryLow, ResolutionEnumLow, ResolutionEnumR360p, ResolutionEnumStandard, ResolutionEnumWebHd, ResolutionEnumStandardHd, ResolutionEnumFullHd, ResolutionEnumQuadHd, ResolutionEnumVrHd, ResolutionEnumFourK, ResolutionEnumFiveK, ResolutionEnumSixK, ResolutionEnumEightK:
return true
}
return false
}
func (e ResolutionEnum) String() string {
return string(e)
}
func (e *ResolutionEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = ResolutionEnum(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid ResolutionEnum", str)
}
return nil
}
func (e ResolutionEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
// GetMaxResolution returns the maximum width or height that media must be
// to qualify as this resolution.
func (e *ResolutionEnum) GetMaxResolution() int {
return resolutionRanges[*e].max
}
// GetMinResolution returns the minimum width or height that media must be
// to qualify as this resolution.
func (e *ResolutionEnum) GetMinResolution() int {
return resolutionRanges[*e].min
}
type StreamingResolutionEnum string
const (
// 240p
StreamingResolutionEnumLow StreamingResolutionEnum = "LOW"
// 480p
StreamingResolutionEnumStandard StreamingResolutionEnum = "STANDARD"
// 720p
StreamingResolutionEnumStandardHd StreamingResolutionEnum = "STANDARD_HD"
// 1080p
StreamingResolutionEnumFullHd StreamingResolutionEnum = "FULL_HD"
// 4k
StreamingResolutionEnumFourK StreamingResolutionEnum = "FOUR_K"
// Original
StreamingResolutionEnumOriginal StreamingResolutionEnum = "ORIGINAL"
)
var AllStreamingResolutionEnum = []StreamingResolutionEnum{
StreamingResolutionEnumLow,
StreamingResolutionEnumStandard,
StreamingResolutionEnumStandardHd,
StreamingResolutionEnumFullHd,
StreamingResolutionEnumFourK,
StreamingResolutionEnumOriginal,
}
func (e StreamingResolutionEnum) IsValid() bool {
switch e {
case StreamingResolutionEnumLow, StreamingResolutionEnumStandard, StreamingResolutionEnumStandardHd, StreamingResolutionEnumFullHd, StreamingResolutionEnumFourK, StreamingResolutionEnumOriginal:
return true
}
return false
}
func (e StreamingResolutionEnum) String() string {
return string(e)
}
func (e *StreamingResolutionEnum) UnmarshalGQL(v interface{}) error {
str, ok := v.(string)
if !ok {
return fmt.Errorf("enums must be strings")
}
*e = StreamingResolutionEnum(str)
if !e.IsValid() {
return fmt.Errorf("%s is not a valid StreamingResolutionEnum", str)
}
return nil
}
func (e StreamingResolutionEnum) MarshalGQL(w io.Writer) {
fmt.Fprint(w, strconv.Quote(e.String()))
}
var streamingResolutionMax = map[StreamingResolutionEnum]int{
StreamingResolutionEnumLow: resolutionRanges[ResolutionEnumLow].min,
StreamingResolutionEnumStandard: resolutionRanges[ResolutionEnumStandard].min,
StreamingResolutionEnumStandardHd: resolutionRanges[ResolutionEnumStandardHd].min,
StreamingResolutionEnumFullHd: resolutionRanges[ResolutionEnumFullHd].min,
StreamingResolutionEnumFourK: resolutionRanges[ResolutionEnumFourK].min,
StreamingResolutionEnumOriginal: 0,
}
func (e StreamingResolutionEnum) GetMaxResolution() int {
return streamingResolutionMax[e]
}

View file

@ -1,5 +1,71 @@
package models package models
type PHashDuplicationCriterionInput struct {
Duplicated *bool `json:"duplicated"`
// Currently unimplemented
Distance *int `json:"distance"`
}
type SceneFilterType struct {
And *SceneFilterType `json:"AND"`
Or *SceneFilterType `json:"OR"`
Not *SceneFilterType `json:"NOT"`
Title *StringCriterionInput `json:"title"`
Details *StringCriterionInput `json:"details"`
// Filter by file oshash
Oshash *StringCriterionInput `json:"oshash"`
// Filter by file checksum
Checksum *StringCriterionInput `json:"checksum"`
// Filter by file phash
Phash *StringCriterionInput `json:"phash"`
// Filter by path
Path *StringCriterionInput `json:"path"`
// Filter by rating
Rating *IntCriterionInput `json:"rating"`
// Filter by organized
Organized *bool `json:"organized"`
// Filter by o-counter
OCounter *IntCriterionInput `json:"o_counter"`
// Filter Scenes that have an exact phash match available
Duplicated *PHashDuplicationCriterionInput `json:"duplicated"`
// Filter by resolution
Resolution *ResolutionCriterionInput `json:"resolution"`
// Filter by duration (in seconds)
Duration *IntCriterionInput `json:"duration"`
// Filter to only include scenes which have markers. `true` or `false`
HasMarkers *string `json:"has_markers"`
// Filter to only include scenes missing this property
IsMissing *string `json:"is_missing"`
// Filter to only include scenes with this studio
Studios *HierarchicalMultiCriterionInput `json:"studios"`
// Filter to only include scenes with this movie
Movies *MultiCriterionInput `json:"movies"`
// Filter to only include scenes with these tags
Tags *HierarchicalMultiCriterionInput `json:"tags"`
// Filter by tag count
TagCount *IntCriterionInput `json:"tag_count"`
// Filter to only include scenes with performers with these tags
PerformerTags *HierarchicalMultiCriterionInput `json:"performer_tags"`
// Filter scenes that have performers that have been favorited
PerformerFavorite *bool `json:"performer_favorite"`
// Filter scenes by performer age at time of scene
PerformerAge *IntCriterionInput `json:"performer_age"`
// Filter to only include scenes with these performers
Performers *MultiCriterionInput `json:"performers"`
// Filter by performer count
PerformerCount *IntCriterionInput `json:"performer_count"`
// Filter by StashID
StashID *StringCriterionInput `json:"stash_id"`
// Filter by url
URL *StringCriterionInput `json:"url"`
// Filter by interactive
Interactive *bool `json:"interactive"`
// Filter by InteractiveSpeed
InteractiveSpeed *IntCriterionInput `json:"interactive_speed"`
Captions *StringCriterionInput `json:"captions"`
}
type SceneQueryOptions struct { type SceneQueryOptions struct {
QueryOptions QueryOptions
SceneFilter *SceneFilterType SceneFilter *SceneFilterType
@ -18,6 +84,18 @@ type SceneQueryResult struct {
resolveErr error resolveErr error
} }
type SceneDestroyInput struct {
ID string `json:"id"`
DeleteFile *bool `json:"delete_file"`
DeleteGenerated *bool `json:"delete_generated"`
}
type ScenesDestroyInput struct {
Ids []string `json:"ids"`
DeleteFile *bool `json:"delete_file"`
DeleteGenerated *bool `json:"delete_generated"`
}
func NewSceneQueryResult(finder SceneFinder) *SceneQueryResult { func NewSceneQueryResult(finder SceneFinder) *SceneQueryResult {
return &SceneQueryResult{ return &SceneQueryResult{
finder: finder, finder: finder,

View file

@ -1,5 +1,22 @@
package models package models
type SceneMarkerFilterType struct {
// Filter to only include scene markers with this tag
TagID *string `json:"tag_id"`
// Filter to only include scene markers with these tags
Tags *HierarchicalMultiCriterionInput `json:"tags"`
// Filter to only include scene markers attached to a scene with these tags
SceneTags *HierarchicalMultiCriterionInput `json:"scene_tags"`
// Filter to only include scene markers with these performers
Performers *MultiCriterionInput `json:"performers"`
}
type MarkerStringsResultType struct {
Count int `json:"count"`
ID string `json:"id"`
Title string `json:"title"`
}
type SceneMarkerReader interface { type SceneMarkerReader interface {
Find(id int) (*SceneMarker, error) Find(id int) (*SceneMarker, error)
FindMany(ids []int) ([]*SceneMarker, error) FindMany(ids []int) ([]*SceneMarker, error)

View file

@ -1,39 +1,13 @@
package models package models
import ( type StashBoxFingerprint struct {
"fmt" Algorithm string `json:"algorithm"`
"strings" Hash string `json:"hash"`
) Duration int `json:"duration"`
}
type StashBoxes []*StashBox
type StashBox struct {
func (sb StashBoxes) ResolveStashBox(source ScraperSourceInput) (*StashBox, error) { Endpoint string `json:"endpoint"`
if source.StashBoxIndex != nil { APIKey string `json:"api_key"`
index := source.StashBoxIndex Name string `json:"name"`
if *index < 0 || *index >= len(sb) {
return nil, fmt.Errorf("%w: invalid stash_box_index: %d", ErrScraperSource, index)
}
return sb[*index], nil
}
if source.StashBoxEndpoint != nil {
var ret *StashBox
endpoint := *source.StashBoxEndpoint
for _, b := range sb {
if strings.EqualFold(endpoint, b.Endpoint) {
ret = b
}
}
if ret == nil {
return nil, fmt.Errorf(`%w: stash-box with endpoint "%s"`, ErrNotFound, endpoint)
}
return ret, nil
}
// neither stash-box inputs were provided, so assume it is a scraper
return nil, nil
} }

View file

@ -1,5 +1,10 @@
package models package models
type StashIDInput struct {
Endpoint string `json:"endpoint"`
StashID string `json:"stash_id"`
}
func StashIDsFromInput(i []*StashIDInput) []StashID { func StashIDsFromInput(i []*StashIDInput) []StashID {
var ret []StashID var ret []StashID
for _, stashID := range i { for _, stashID := range i {

View file

@ -1,5 +1,33 @@
package models package models
type StudioFilterType struct {
And *StudioFilterType `json:"AND"`
Or *StudioFilterType `json:"OR"`
Not *StudioFilterType `json:"NOT"`
Name *StringCriterionInput `json:"name"`
Details *StringCriterionInput `json:"details"`
// Filter to only include studios with this parent studio
Parents *MultiCriterionInput `json:"parents"`
// Filter by StashID
StashID *StringCriterionInput `json:"stash_id"`
// Filter to only include studios missing this property
IsMissing *string `json:"is_missing"`
// Filter by rating
Rating *IntCriterionInput `json:"rating"`
// Filter by scene count
SceneCount *IntCriterionInput `json:"scene_count"`
// Filter by image count
ImageCount *IntCriterionInput `json:"image_count"`
// Filter by gallery count
GalleryCount *IntCriterionInput `json:"gallery_count"`
// Filter by url
URL *StringCriterionInput `json:"url"`
// Filter by studio aliases
Aliases *StringCriterionInput `json:"aliases"`
// Filter by autotag ignore value
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
}
type StudioReader interface { type StudioReader interface {
Find(id int) (*Studio, error) Find(id int) (*Studio, error)
FindMany(ids []int) ([]*Studio, error) FindMany(ids []int) ([]*Studio, error)

View file

@ -1,5 +1,37 @@
package models package models
type TagFilterType struct {
And *TagFilterType `json:"AND"`
Or *TagFilterType `json:"OR"`
Not *TagFilterType `json:"NOT"`
// Filter by tag name
Name *StringCriterionInput `json:"name"`
// Filter by tag aliases
Aliases *StringCriterionInput `json:"aliases"`
// Filter to only include tags missing this property
IsMissing *string `json:"is_missing"`
// Filter by number of scenes with this tag
SceneCount *IntCriterionInput `json:"scene_count"`
// Filter by number of images with this tag
ImageCount *IntCriterionInput `json:"image_count"`
// Filter by number of galleries with this tag
GalleryCount *IntCriterionInput `json:"gallery_count"`
// Filter by number of performers with this tag
PerformerCount *IntCriterionInput `json:"performer_count"`
// Filter by number of markers with this tag
MarkerCount *IntCriterionInput `json:"marker_count"`
// Filter by parent tags
Parents *HierarchicalMultiCriterionInput `json:"parents"`
// Filter by child tags
Children *HierarchicalMultiCriterionInput `json:"children"`
// Filter by number of parent tags the tag has
ParentCount *IntCriterionInput `json:"parent_count"`
// Filter by number f child tags the tag has
ChildCount *IntCriterionInput `json:"child_count"`
// Filter by autotag ignore value
IgnoreAutoTag *bool `json:"ignore_auto_tag"`
}
type TagReader interface { type TagReader interface {
Find(id int) (*Tag, error) Find(id int) (*Tag, error)
FindMany(ids []int) ([]*Tag, error) FindMany(ids []int) ([]*Tag, error)

View file

@ -1,10 +1,20 @@
package plugin package plugin
import ( type PluginArgInput struct {
"github.com/stashapp/stash/pkg/models" Key string `json:"key"`
) Value *PluginValueInput `json:"value"`
}
func findArg(args []*models.PluginArgInput, name string) *models.PluginArgInput { type PluginValueInput struct {
Str *string `json:"str"`
I *int `json:"i"`
B *bool `json:"b"`
F *float64 `json:"f"`
O []*PluginArgInput `json:"o"`
A []*PluginValueInput `json:"a"`
}
func findArg(args []*PluginArgInput, name string) *PluginArgInput {
for _, v := range args { for _, v := range args {
if v.Key == name { if v.Key == name {
return v return v
@ -14,13 +24,13 @@ func findArg(args []*models.PluginArgInput, name string) *models.PluginArgInput
return nil return nil
} }
func applyDefaultArgs(args []*models.PluginArgInput, defaultArgs map[string]string) []*models.PluginArgInput { func applyDefaultArgs(args []*PluginArgInput, defaultArgs map[string]string) []*PluginArgInput {
for k, v := range defaultArgs { for k, v := range defaultArgs {
if arg := findArg(args, k); arg == nil { if arg := findArg(args, k); arg == nil {
v := v // Copy v, because it's being exported out of the loop v := v // Copy v, because it's being exported out of the loop
args = append(args, &models.PluginArgInput{ args = append(args, &PluginArgInput{
Key: k, Key: k,
Value: &models.PluginValueInput{ Value: &PluginValueInput{
Str: &v, Str: &v,
}, },
}) })

View file

@ -8,7 +8,6 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/stashapp/stash/pkg/models"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -59,11 +58,11 @@ type Config struct {
Hooks []*HookConfig `yaml:"hooks"` Hooks []*HookConfig `yaml:"hooks"`
} }
func (c Config) getPluginTasks(includePlugin bool) []*models.PluginTask { func (c Config) getPluginTasks(includePlugin bool) []*PluginTask {
var ret []*models.PluginTask var ret []*PluginTask
for _, o := range c.Tasks { for _, o := range c.Tasks {
task := &models.PluginTask{ task := &PluginTask{
Name: o.Name, Name: o.Name,
Description: &o.Description, Description: &o.Description,
} }
@ -77,11 +76,11 @@ func (c Config) getPluginTasks(includePlugin bool) []*models.PluginTask {
return ret return ret
} }
func (c Config) getPluginHooks(includePlugin bool) []*models.PluginHook { func (c Config) getPluginHooks(includePlugin bool) []*PluginHook {
var ret []*models.PluginHook var ret []*PluginHook
for _, o := range c.Hooks { for _, o := range c.Hooks {
hook := &models.PluginHook{ hook := &PluginHook{
Name: o.Name, Name: o.Name,
Description: &o.Description, Description: &o.Description,
Hooks: convertHooks(o.TriggeredBy), Hooks: convertHooks(o.TriggeredBy),
@ -113,8 +112,8 @@ func (c Config) getName() string {
return c.id return c.id
} }
func (c Config) toPlugin() *models.Plugin { func (c Config) toPlugin() *Plugin {
return &models.Plugin{ return &Plugin{
ID: c.id, ID: c.id,
Name: c.getName(), Name: c.getName(),
Description: c.Description, Description: c.Description,

View file

@ -1,11 +1,10 @@
package plugin package plugin
import ( import (
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/plugin/common" "github.com/stashapp/stash/pkg/plugin/common"
) )
func toPluginArgs(args []*models.PluginArgInput) common.ArgsMap { func toPluginArgs(args []*PluginArgInput) common.ArgsMap {
ret := make(common.ArgsMap) ret := make(common.ArgsMap)
for _, a := range args { for _, a := range args {
ret[a.Key] = toPluginArgValue(a.Value) ret[a.Key] = toPluginArgValue(a.Value)
@ -14,7 +13,7 @@ func toPluginArgs(args []*models.PluginArgInput) common.ArgsMap {
return ret return ret
} }
func toPluginArgValue(arg *models.PluginValueInput) common.PluginArgValue { func toPluginArgValue(arg *PluginValueInput) common.PluginArgValue {
if arg == nil { if arg == nil {
return nil return nil
} }

View file

@ -5,6 +5,13 @@ import (
"github.com/stashapp/stash/pkg/plugin/common" "github.com/stashapp/stash/pkg/plugin/common"
) )
type PluginHook struct {
Name string `json:"name"`
Description *string `json:"description"`
Hooks []string `json:"hooks"`
Plugin *Plugin `json:"plugin"`
}
type HookTriggerEnum string type HookTriggerEnum string
// Scan-related hooks are current disabled until post-hook execution is // Scan-related hooks are current disabled until post-hook execution is

View file

@ -22,6 +22,16 @@ import (
"github.com/stashapp/stash/pkg/sliceutil/stringslice" "github.com/stashapp/stash/pkg/sliceutil/stringslice"
) )
type Plugin struct {
ID string `json:"id"`
Name string `json:"name"`
Description *string `json:"description"`
URL *string `json:"url"`
Version *string `json:"version"`
Tasks []*PluginTask `json:"tasks"`
Hooks []*PluginHook `json:"hooks"`
}
type ServerConfig interface { type ServerConfig interface {
GetHost() string GetHost() string
GetPort() int GetPort() int
@ -103,8 +113,8 @@ func loadPlugins(path string) ([]Config, error) {
} }
// ListPlugins returns plugin details for all of the loaded plugins. // ListPlugins returns plugin details for all of the loaded plugins.
func (c Cache) ListPlugins() []*models.Plugin { func (c Cache) ListPlugins() []*Plugin {
var ret []*models.Plugin var ret []*Plugin
for _, s := range c.plugins { for _, s := range c.plugins {
ret = append(ret, s.toPlugin()) ret = append(ret, s.toPlugin())
} }
@ -113,8 +123,8 @@ func (c Cache) ListPlugins() []*models.Plugin {
} }
// ListPluginTasks returns all runnable plugin tasks in all loaded plugins. // ListPluginTasks returns all runnable plugin tasks in all loaded plugins.
func (c Cache) ListPluginTasks() []*models.PluginTask { func (c Cache) ListPluginTasks() []*PluginTask {
var ret []*models.PluginTask var ret []*PluginTask
for _, s := range c.plugins { for _, s := range c.plugins {
ret = append(ret, s.getPluginTasks(true)...) ret = append(ret, s.getPluginTasks(true)...)
} }
@ -122,7 +132,7 @@ func (c Cache) ListPluginTasks() []*models.PluginTask {
return ret return ret
} }
func buildPluginInput(plugin *Config, operation *OperationConfig, serverConnection common.StashServerConnection, args []*models.PluginArgInput) common.PluginInput { func buildPluginInput(plugin *Config, operation *OperationConfig, serverConnection common.StashServerConnection, args []*PluginArgInput) common.PluginInput {
args = applyDefaultArgs(args, operation.DefaultArgs) args = applyDefaultArgs(args, operation.DefaultArgs)
serverConnection.PluginDir = plugin.getConfigPath() serverConnection.PluginDir = plugin.getConfigPath()
return common.PluginInput{ return common.PluginInput{
@ -152,7 +162,7 @@ func (c Cache) makeServerConnection(ctx context.Context) common.StashServerConne
// CreateTask runs the plugin operation for the pluginID and operation // CreateTask runs the plugin operation for the pluginID and operation
// name provided. Returns an error if the plugin or the operation could not be // name provided. Returns an error if the plugin or the operation could not be
// resolved. // resolved.
func (c Cache) CreateTask(ctx context.Context, pluginID string, operationName string, args []*models.PluginArgInput, progress chan float64) (Task, error) { func (c Cache) CreateTask(ctx context.Context, pluginID string, operationName string, args []*PluginArgInput, progress chan float64) (Task, error) {
serverConnection := c.makeServerConnection(ctx) serverConnection := c.makeServerConnection(ctx)
// find the plugin and operation // find the plugin and operation

View file

@ -6,6 +6,12 @@ import (
"github.com/stashapp/stash/pkg/plugin/common" "github.com/stashapp/stash/pkg/plugin/common"
) )
type PluginTask struct {
Name string `json:"name"`
Description *string `json:"description"`
Plugin *Plugin `json:"plugin"`
}
// Task is the interface that handles management of a single plugin task. // Task is the interface that handles management of a single plugin task.
type Task interface { type Task interface {
// Start starts the plugin task. Returns an error if task could not be // Start starts the plugin task. Returns an error if task could not be

View file

@ -25,12 +25,12 @@ func (e scraperAction) IsValid() bool {
} }
type scraperActionImpl interface { type scraperActionImpl interface {
scrapeByURL(ctx context.Context, url string, ty models.ScrapeContentType) (models.ScrapedContent, error) scrapeByURL(ctx context.Context, url string, ty ScrapeContentType) (ScrapedContent, error)
scrapeByName(ctx context.Context, name string, ty models.ScrapeContentType) ([]models.ScrapedContent, error) scrapeByName(ctx context.Context, name string, ty ScrapeContentType) ([]ScrapedContent, error)
scrapeByFragment(ctx context.Context, input Input) (models.ScrapedContent, error) scrapeByFragment(ctx context.Context, input Input) (ScrapedContent, error)
scrapeSceneByScene(ctx context.Context, scene *models.Scene) (*models.ScrapedScene, error) scrapeSceneByScene(ctx context.Context, scene *models.Scene) (*ScrapedScene, error)
scrapeGalleryByGallery(ctx context.Context, gallery *models.Gallery) (*models.ScrapedGallery, error) scrapeGalleryByGallery(ctx context.Context, gallery *models.Gallery) (*ScrapedGallery, error)
} }
func (c config) getScraper(scraper scraperTypeConfig, client *http.Client, txnManager models.TransactionManager, globalConfig GlobalConfig) scraperActionImpl { func (c config) getScraper(scraper scraperTypeConfig, client *http.Client, txnManager models.TransactionManager, globalConfig GlobalConfig) scraperActionImpl {

View file

@ -83,8 +83,8 @@ func autotagMatchTags(path string, tagReader models.TagReader, trimExt bool) ([]
return ret, nil return ret, nil
} }
func (s autotagScraper) viaScene(ctx context.Context, _client *http.Client, scene *models.Scene) (*models.ScrapedScene, error) { func (s autotagScraper) viaScene(ctx context.Context, _client *http.Client, scene *models.Scene) (*ScrapedScene, error) {
var ret *models.ScrapedScene var ret *ScrapedScene
const trimExt = false const trimExt = false
// populate performers, studio and tags based on scene path // populate performers, studio and tags based on scene path
@ -105,7 +105,7 @@ func (s autotagScraper) viaScene(ctx context.Context, _client *http.Client, scen
} }
if len(performers) > 0 || studio != nil || len(tags) > 0 { if len(performers) > 0 || studio != nil || len(tags) > 0 {
ret = &models.ScrapedScene{ ret = &ScrapedScene{
Performers: performers, Performers: performers,
Studio: studio, Studio: studio,
Tags: tags, Tags: tags,
@ -120,7 +120,7 @@ func (s autotagScraper) viaScene(ctx context.Context, _client *http.Client, scen
return ret, nil return ret, nil
} }
func (s autotagScraper) viaGallery(ctx context.Context, _client *http.Client, gallery *models.Gallery) (*models.ScrapedGallery, error) { func (s autotagScraper) viaGallery(ctx context.Context, _client *http.Client, gallery *models.Gallery) (*ScrapedGallery, error) {
if !gallery.Path.Valid { if !gallery.Path.Valid {
// not valid for non-path-based galleries // not valid for non-path-based galleries
return nil, nil return nil, nil
@ -129,7 +129,7 @@ func (s autotagScraper) viaGallery(ctx context.Context, _client *http.Client, ga
// only trim extension if gallery is file-based // only trim extension if gallery is file-based
trimExt := gallery.Zip trimExt := gallery.Zip
var ret *models.ScrapedGallery var ret *ScrapedGallery
// populate performers, studio and tags based on scene path // populate performers, studio and tags based on scene path
if err := s.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error { if err := s.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error {
@ -149,7 +149,7 @@ func (s autotagScraper) viaGallery(ctx context.Context, _client *http.Client, ga
} }
if len(performers) > 0 || studio != nil || len(tags) > 0 { if len(performers) > 0 || studio != nil || len(tags) > 0 {
ret = &models.ScrapedGallery{ ret = &ScrapedGallery{
Performers: performers, Performers: performers,
Studio: studio, Studio: studio,
Tags: tags, Tags: tags,
@ -164,33 +164,33 @@ func (s autotagScraper) viaGallery(ctx context.Context, _client *http.Client, ga
return ret, nil return ret, nil
} }
func (s autotagScraper) supports(ty models.ScrapeContentType) bool { func (s autotagScraper) supports(ty ScrapeContentType) bool {
switch ty { switch ty {
case models.ScrapeContentTypeScene: case ScrapeContentTypeScene:
return true return true
case models.ScrapeContentTypeGallery: case ScrapeContentTypeGallery:
return true return true
} }
return false return false
} }
func (s autotagScraper) supportsURL(url string, ty models.ScrapeContentType) bool { func (s autotagScraper) supportsURL(url string, ty ScrapeContentType) bool {
return false return false
} }
func (s autotagScraper) spec() models.Scraper { func (s autotagScraper) spec() Scraper {
supportedScrapes := []models.ScrapeType{ supportedScrapes := []ScrapeType{
models.ScrapeTypeFragment, ScrapeTypeFragment,
} }
return models.Scraper{ return Scraper{
ID: autoTagScraperID, ID: autoTagScraperID,
Name: autoTagScraperName, Name: autoTagScraperName,
Scene: &models.ScraperSpec{ Scene: &ScraperSpec{
SupportedScrapes: supportedScrapes, SupportedScrapes: supportedScrapes,
}, },
Gallery: &models.ScraperSpec{ Gallery: &ScraperSpec{
SupportedScrapes: supportedScrapes, SupportedScrapes: supportedScrapes,
}, },
} }

View file

@ -148,8 +148,8 @@ func (c *Cache) ReloadScrapers() error {
// ListScrapers lists scrapers matching one of the given types. // ListScrapers lists scrapers matching one of the given types.
// Returns a list of scrapers, sorted by their ID. // Returns a list of scrapers, sorted by their ID.
func (c Cache) ListScrapers(tys []models.ScrapeContentType) []*models.Scraper { func (c Cache) ListScrapers(tys []ScrapeContentType) []*Scraper {
var ret []*models.Scraper var ret []*Scraper
for _, s := range c.scrapers { for _, s := range c.scrapers {
for _, t := range tys { for _, t := range tys {
if s.supports(t) { if s.supports(t) {
@ -168,7 +168,7 @@ func (c Cache) ListScrapers(tys []models.ScrapeContentType) []*models.Scraper {
} }
// GetScraper returns the scraper matching the provided id. // GetScraper returns the scraper matching the provided id.
func (c Cache) GetScraper(scraperID string) *models.Scraper { func (c Cache) GetScraper(scraperID string) *Scraper {
s := c.findScraper(scraperID) s := c.findScraper(scraperID)
if s != nil { if s != nil {
spec := s.spec() spec := s.spec()
@ -187,7 +187,7 @@ func (c Cache) findScraper(scraperID string) scraper {
return nil return nil
} }
func (c Cache) ScrapeName(ctx context.Context, id, query string, ty models.ScrapeContentType) ([]models.ScrapedContent, error) { func (c Cache) ScrapeName(ctx context.Context, id, query string, ty ScrapeContentType) ([]ScrapedContent, error) {
// find scraper with the provided id // find scraper with the provided id
s := c.findScraper(id) s := c.findScraper(id)
if s == nil { if s == nil {
@ -206,7 +206,7 @@ func (c Cache) ScrapeName(ctx context.Context, id, query string, ty models.Scrap
} }
// ScrapeFragment uses the given fragment input to scrape // ScrapeFragment uses the given fragment input to scrape
func (c Cache) ScrapeFragment(ctx context.Context, id string, input Input) (models.ScrapedContent, error) { func (c Cache) ScrapeFragment(ctx context.Context, id string, input Input) (ScrapedContent, error) {
s := c.findScraper(id) s := c.findScraper(id)
if s == nil { if s == nil {
return nil, fmt.Errorf("%w: id %s", ErrNotFound, id) return nil, fmt.Errorf("%w: id %s", ErrNotFound, id)
@ -228,7 +228,7 @@ func (c Cache) ScrapeFragment(ctx context.Context, id string, input Input) (mode
// ScrapeURL scrapes a given url for the given content. Searches the scraper cache // ScrapeURL scrapes a given url for the given content. Searches the scraper cache
// and picks the first scraper capable of scraping the given url into the desired // and picks the first scraper capable of scraping the given url into the desired
// content. Returns the scraped content or an error if the scrape fails. // content. Returns the scraped content or an error if the scrape fails.
func (c Cache) ScrapeURL(ctx context.Context, url string, ty models.ScrapeContentType) (models.ScrapedContent, error) { func (c Cache) ScrapeURL(ctx context.Context, url string, ty ScrapeContentType) (ScrapedContent, error) {
for _, s := range c.scrapers { for _, s := range c.scrapers {
if s.supportsURL(url, ty) { if s.supportsURL(url, ty) {
ul, ok := s.(urlScraper) ul, ok := s.(urlScraper)
@ -251,7 +251,7 @@ func (c Cache) ScrapeURL(ctx context.Context, url string, ty models.ScrapeConten
return nil, nil return nil, nil
} }
func (c Cache) ScrapeID(ctx context.Context, scraperID string, id int, ty models.ScrapeContentType) (models.ScrapedContent, error) { func (c Cache) ScrapeID(ctx context.Context, scraperID string, id int, ty ScrapeContentType) (ScrapedContent, error) {
s := c.findScraper(scraperID) s := c.findScraper(scraperID)
if s == nil { if s == nil {
return nil, fmt.Errorf("%w: id %s", ErrNotFound, scraperID) return nil, fmt.Errorf("%w: id %s", ErrNotFound, scraperID)
@ -261,9 +261,9 @@ func (c Cache) ScrapeID(ctx context.Context, scraperID string, id int, ty models
return nil, fmt.Errorf("%w: cannot use scraper %s to scrape %v content", ErrNotSupported, scraperID, ty) return nil, fmt.Errorf("%w: cannot use scraper %s to scrape %v content", ErrNotSupported, scraperID, ty)
} }
var ret models.ScrapedContent var ret ScrapedContent
switch ty { switch ty {
case models.ScrapeContentTypeScene: case ScrapeContentTypeScene:
ss, ok := s.(sceneScraper) ss, ok := s.(sceneScraper)
if !ok { if !ok {
return nil, fmt.Errorf("%w: cannot use scraper %s as a scene scraper", ErrNotSupported, scraperID) return nil, fmt.Errorf("%w: cannot use scraper %s as a scene scraper", ErrNotSupported, scraperID)
@ -284,7 +284,7 @@ func (c Cache) ScrapeID(ctx context.Context, scraperID string, id int, ty models
if scraped != nil { if scraped != nil {
ret = scraped ret = scraped
} }
case models.ScrapeContentTypeGallery: case ScrapeContentTypeGallery:
gs, ok := s.(galleryScraper) gs, ok := s.(galleryScraper)
if !ok { if !ok {
return nil, fmt.Errorf("%w: cannot use scraper %s as a gallery scraper", ErrNotSupported, scraperID) return nil, fmt.Errorf("%w: cannot use scraper %s as a gallery scraper", ErrNotSupported, scraperID)

View file

@ -8,7 +8,6 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/stashapp/stash/pkg/models"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -233,21 +232,21 @@ func loadConfigFromYAMLFile(path string) (*config, error) {
return ret, nil return ret, nil
} }
func (c config) spec() models.Scraper { func (c config) spec() Scraper {
ret := models.Scraper{ ret := Scraper{
ID: c.ID, ID: c.ID,
Name: c.Name, Name: c.Name,
} }
performer := models.ScraperSpec{} performer := ScraperSpec{}
if c.PerformerByName != nil { if c.PerformerByName != nil {
performer.SupportedScrapes = append(performer.SupportedScrapes, models.ScrapeTypeName) performer.SupportedScrapes = append(performer.SupportedScrapes, ScrapeTypeName)
} }
if c.PerformerByFragment != nil { if c.PerformerByFragment != nil {
performer.SupportedScrapes = append(performer.SupportedScrapes, models.ScrapeTypeFragment) performer.SupportedScrapes = append(performer.SupportedScrapes, ScrapeTypeFragment)
} }
if len(c.PerformerByURL) > 0 { if len(c.PerformerByURL) > 0 {
performer.SupportedScrapes = append(performer.SupportedScrapes, models.ScrapeTypeURL) performer.SupportedScrapes = append(performer.SupportedScrapes, ScrapeTypeURL)
for _, v := range c.PerformerByURL { for _, v := range c.PerformerByURL {
performer.Urls = append(performer.Urls, v.URL...) performer.Urls = append(performer.Urls, v.URL...)
} }
@ -257,15 +256,15 @@ func (c config) spec() models.Scraper {
ret.Performer = &performer ret.Performer = &performer
} }
scene := models.ScraperSpec{} scene := ScraperSpec{}
if c.SceneByFragment != nil { if c.SceneByFragment != nil {
scene.SupportedScrapes = append(scene.SupportedScrapes, models.ScrapeTypeFragment) scene.SupportedScrapes = append(scene.SupportedScrapes, ScrapeTypeFragment)
} }
if c.SceneByName != nil && c.SceneByQueryFragment != nil { if c.SceneByName != nil && c.SceneByQueryFragment != nil {
scene.SupportedScrapes = append(scene.SupportedScrapes, models.ScrapeTypeName) scene.SupportedScrapes = append(scene.SupportedScrapes, ScrapeTypeName)
} }
if len(c.SceneByURL) > 0 { if len(c.SceneByURL) > 0 {
scene.SupportedScrapes = append(scene.SupportedScrapes, models.ScrapeTypeURL) scene.SupportedScrapes = append(scene.SupportedScrapes, ScrapeTypeURL)
for _, v := range c.SceneByURL { for _, v := range c.SceneByURL {
scene.Urls = append(scene.Urls, v.URL...) scene.Urls = append(scene.Urls, v.URL...)
} }
@ -275,12 +274,12 @@ func (c config) spec() models.Scraper {
ret.Scene = &scene ret.Scene = &scene
} }
gallery := models.ScraperSpec{} gallery := ScraperSpec{}
if c.GalleryByFragment != nil { if c.GalleryByFragment != nil {
gallery.SupportedScrapes = append(gallery.SupportedScrapes, models.ScrapeTypeFragment) gallery.SupportedScrapes = append(gallery.SupportedScrapes, ScrapeTypeFragment)
} }
if len(c.GalleryByURL) > 0 { if len(c.GalleryByURL) > 0 {
gallery.SupportedScrapes = append(gallery.SupportedScrapes, models.ScrapeTypeURL) gallery.SupportedScrapes = append(gallery.SupportedScrapes, ScrapeTypeURL)
for _, v := range c.GalleryByURL { for _, v := range c.GalleryByURL {
gallery.Urls = append(gallery.Urls, v.URL...) gallery.Urls = append(gallery.Urls, v.URL...)
} }
@ -290,9 +289,9 @@ func (c config) spec() models.Scraper {
ret.Gallery = &gallery ret.Gallery = &gallery
} }
movie := models.ScraperSpec{} movie := ScraperSpec{}
if len(c.MovieByURL) > 0 { if len(c.MovieByURL) > 0 {
movie.SupportedScrapes = append(movie.SupportedScrapes, models.ScrapeTypeURL) movie.SupportedScrapes = append(movie.SupportedScrapes, ScrapeTypeURL)
for _, v := range c.MovieByURL { for _, v := range c.MovieByURL {
movie.Urls = append(movie.Urls, v.URL...) movie.Urls = append(movie.Urls, v.URL...)
} }
@ -305,42 +304,42 @@ func (c config) spec() models.Scraper {
return ret return ret
} }
func (c config) supports(ty models.ScrapeContentType) bool { func (c config) supports(ty ScrapeContentType) bool {
switch ty { switch ty {
case models.ScrapeContentTypePerformer: case ScrapeContentTypePerformer:
return c.PerformerByName != nil || c.PerformerByFragment != nil || len(c.PerformerByURL) > 0 return c.PerformerByName != nil || c.PerformerByFragment != nil || len(c.PerformerByURL) > 0
case models.ScrapeContentTypeScene: case ScrapeContentTypeScene:
return (c.SceneByName != nil && c.SceneByQueryFragment != nil) || c.SceneByFragment != nil || len(c.SceneByURL) > 0 return (c.SceneByName != nil && c.SceneByQueryFragment != nil) || c.SceneByFragment != nil || len(c.SceneByURL) > 0
case models.ScrapeContentTypeGallery: case ScrapeContentTypeGallery:
return c.GalleryByFragment != nil || len(c.GalleryByURL) > 0 return c.GalleryByFragment != nil || len(c.GalleryByURL) > 0
case models.ScrapeContentTypeMovie: case ScrapeContentTypeMovie:
return len(c.MovieByURL) > 0 return len(c.MovieByURL) > 0
} }
panic("Unhandled ScrapeContentType") panic("Unhandled ScrapeContentType")
} }
func (c config) matchesURL(url string, ty models.ScrapeContentType) bool { func (c config) matchesURL(url string, ty ScrapeContentType) bool {
switch ty { switch ty {
case models.ScrapeContentTypePerformer: case ScrapeContentTypePerformer:
for _, scraper := range c.PerformerByURL { for _, scraper := range c.PerformerByURL {
if scraper.matchesURL(url) { if scraper.matchesURL(url) {
return true return true
} }
} }
case models.ScrapeContentTypeScene: case ScrapeContentTypeScene:
for _, scraper := range c.SceneByURL { for _, scraper := range c.SceneByURL {
if scraper.matchesURL(url) { if scraper.matchesURL(url) {
return true return true
} }
} }
case models.ScrapeContentTypeGallery: case ScrapeContentTypeGallery:
for _, scraper := range c.GalleryByURL { for _, scraper := range c.GalleryByURL {
if scraper.matchesURL(url) { if scraper.matchesURL(url) {
return true return true
} }
} }
case models.ScrapeContentTypeMovie: case ScrapeContentTypeMovie:
for _, scraper := range c.MovieByURL { for _, scraper := range c.MovieByURL {
if scraper.matchesURL(url) { if scraper.matchesURL(url) {
return true return true

22
pkg/scraper/gallery.go Normal file
View file

@ -0,0 +1,22 @@
package scraper
import "github.com/stashapp/stash/pkg/models"
type ScrapedGallery struct {
Title *string `json:"title"`
Details *string `json:"details"`
URL *string `json:"url"`
Date *string `json:"date"`
Studio *models.ScrapedStudio `json:"studio"`
Tags []*models.ScrapedTag `json:"tags"`
Performers []*models.ScrapedPerformer `json:"performers"`
}
func (ScrapedGallery) IsScrapedContent() {}
type ScrapedGalleryInput struct {
Title *string `json:"title"`
Details *string `json:"details"`
URL *string `json:"url"`
Date *string `json:"date"`
}

View file

@ -23,7 +23,7 @@ func newGroupScraper(c config, txnManager models.TransactionManager, globalConfi
} }
} }
func (g group) spec() models.Scraper { func (g group) spec() Scraper {
return g.config.spec() return g.config.spec()
} }
@ -42,14 +42,14 @@ func (g group) fragmentScraper(input Input) *scraperTypeConfig {
return nil return nil
} }
func (g group) viaFragment(ctx context.Context, client *http.Client, input Input) (models.ScrapedContent, error) { func (g group) viaFragment(ctx context.Context, client *http.Client, input Input) (ScrapedContent, error) {
stc := g.fragmentScraper(input) stc := g.fragmentScraper(input)
if stc == nil { if stc == nil {
// If there's no performer fragment scraper in the group, we try to use // If there's no performer fragment scraper in the group, we try to use
// the URL scraper. Check if there's an URL in the input, and then shift // the URL scraper. Check if there's an URL in the input, and then shift
// to an URL scrape if it's present. // to an URL scrape if it's present.
if input.Performer != nil && input.Performer.URL != nil && *input.Performer.URL != "" { if input.Performer != nil && input.Performer.URL != nil && *input.Performer.URL != "" {
return g.viaURL(ctx, client, *input.Performer.URL, models.ScrapeContentTypePerformer) return g.viaURL(ctx, client, *input.Performer.URL, ScrapeContentTypePerformer)
} }
return nil, ErrNotSupported return nil, ErrNotSupported
@ -59,7 +59,7 @@ func (g group) viaFragment(ctx context.Context, client *http.Client, input Input
return s.scrapeByFragment(ctx, input) return s.scrapeByFragment(ctx, input)
} }
func (g group) viaScene(ctx context.Context, client *http.Client, scene *models.Scene) (*models.ScrapedScene, error) { func (g group) viaScene(ctx context.Context, client *http.Client, scene *models.Scene) (*ScrapedScene, error) {
if g.config.SceneByFragment == nil { if g.config.SceneByFragment == nil {
return nil, ErrNotSupported return nil, ErrNotSupported
} }
@ -68,7 +68,7 @@ func (g group) viaScene(ctx context.Context, client *http.Client, scene *models.
return s.scrapeSceneByScene(ctx, scene) return s.scrapeSceneByScene(ctx, scene)
} }
func (g group) viaGallery(ctx context.Context, client *http.Client, gallery *models.Gallery) (*models.ScrapedGallery, error) { func (g group) viaGallery(ctx context.Context, client *http.Client, gallery *models.Gallery) (*ScrapedGallery, error) {
if g.config.GalleryByFragment == nil { if g.config.GalleryByFragment == nil {
return nil, ErrNotSupported return nil, ErrNotSupported
} }
@ -77,22 +77,22 @@ func (g group) viaGallery(ctx context.Context, client *http.Client, gallery *mod
return s.scrapeGalleryByGallery(ctx, gallery) return s.scrapeGalleryByGallery(ctx, gallery)
} }
func loadUrlCandidates(c config, ty models.ScrapeContentType) []*scrapeByURLConfig { func loadUrlCandidates(c config, ty ScrapeContentType) []*scrapeByURLConfig {
switch ty { switch ty {
case models.ScrapeContentTypePerformer: case ScrapeContentTypePerformer:
return c.PerformerByURL return c.PerformerByURL
case models.ScrapeContentTypeScene: case ScrapeContentTypeScene:
return c.SceneByURL return c.SceneByURL
case models.ScrapeContentTypeMovie: case ScrapeContentTypeMovie:
return c.MovieByURL return c.MovieByURL
case models.ScrapeContentTypeGallery: case ScrapeContentTypeGallery:
return c.GalleryByURL return c.GalleryByURL
} }
panic("loadUrlCandidates: unreachable") panic("loadUrlCandidates: unreachable")
} }
func (g group) viaURL(ctx context.Context, client *http.Client, url string, ty models.ScrapeContentType) (models.ScrapedContent, error) { func (g group) viaURL(ctx context.Context, client *http.Client, url string, ty ScrapeContentType) (ScrapedContent, error) {
candidates := loadUrlCandidates(g.config, ty) candidates := loadUrlCandidates(g.config, ty)
for _, scraper := range candidates { for _, scraper := range candidates {
if scraper.matchesURL(url) { if scraper.matchesURL(url) {
@ -111,16 +111,16 @@ func (g group) viaURL(ctx context.Context, client *http.Client, url string, ty m
return nil, nil return nil, nil
} }
func (g group) viaName(ctx context.Context, client *http.Client, name string, ty models.ScrapeContentType) ([]models.ScrapedContent, error) { func (g group) viaName(ctx context.Context, client *http.Client, name string, ty ScrapeContentType) ([]ScrapedContent, error) {
switch ty { switch ty {
case models.ScrapeContentTypePerformer: case ScrapeContentTypePerformer:
if g.config.PerformerByName == nil { if g.config.PerformerByName == nil {
break break
} }
s := g.config.getScraper(*g.config.PerformerByName, client, g.txnManager, g.globalConf) s := g.config.getScraper(*g.config.PerformerByName, client, g.txnManager, g.globalConf)
return s.scrapeByName(ctx, name, ty) return s.scrapeByName(ctx, name, ty)
case models.ScrapeContentTypeScene: case ScrapeContentTypeScene:
if g.config.SceneByName == nil { if g.config.SceneByName == nil {
break break
} }
@ -132,10 +132,10 @@ func (g group) viaName(ctx context.Context, client *http.Client, name string, ty
return nil, fmt.Errorf("%w: cannot load %v by name", ErrNotSupported, ty) return nil, fmt.Errorf("%w: cannot load %v by name", ErrNotSupported, ty)
} }
func (g group) supports(ty models.ScrapeContentType) bool { func (g group) supports(ty ScrapeContentType) bool {
return g.config.supports(ty) return g.config.supports(ty)
} }
func (g group) supportsURL(url string, ty models.ScrapeContentType) bool { func (g group) supportsURL(url string, ty ScrapeContentType) bool {
return g.config.matchesURL(url, ty) return g.config.matchesURL(url, ty)
} }

View file

@ -29,7 +29,7 @@ func setPerformerImage(ctx context.Context, client *http.Client, p *models.Scrap
return nil return nil
} }
func setSceneImage(ctx context.Context, client *http.Client, s *models.ScrapedScene, globalConfig GlobalConfig) error { func setSceneImage(ctx context.Context, client *http.Client, s *ScrapedScene, globalConfig GlobalConfig) error {
// don't try to get the image if it doesn't appear to be a URL // don't try to get the image if it doesn't appear to be a URL
if s.Image == nil || !strings.HasPrefix(*s.Image, "http") { if s.Image == nil || !strings.HasPrefix(*s.Image, "http") {
// nothing to do // nothing to do

View file

@ -75,7 +75,7 @@ func (s *jsonScraper) loadURL(ctx context.Context, url string) (string, error) {
return docStr, err return docStr, err
} }
func (s *jsonScraper) scrapeByURL(ctx context.Context, url string, ty models.ScrapeContentType) (models.ScrapedContent, error) { func (s *jsonScraper) scrapeByURL(ctx context.Context, url string, ty ScrapeContentType) (ScrapedContent, error) {
u := replaceURL(url, s.scraper) // allow a URL Replace for url-queries u := replaceURL(url, s.scraper) // allow a URL Replace for url-queries
doc, scraper, err := s.scrapeURL(ctx, u) doc, scraper, err := s.scrapeURL(ctx, u)
if err != nil { if err != nil {
@ -84,20 +84,20 @@ func (s *jsonScraper) scrapeByURL(ctx context.Context, url string, ty models.Scr
q := s.getJsonQuery(doc) q := s.getJsonQuery(doc)
switch ty { switch ty {
case models.ScrapeContentTypePerformer: case ScrapeContentTypePerformer:
return scraper.scrapePerformer(ctx, q) return scraper.scrapePerformer(ctx, q)
case models.ScrapeContentTypeScene: case ScrapeContentTypeScene:
return scraper.scrapeScene(ctx, q) return scraper.scrapeScene(ctx, q)
case models.ScrapeContentTypeGallery: case ScrapeContentTypeGallery:
return scraper.scrapeGallery(ctx, q) return scraper.scrapeGallery(ctx, q)
case models.ScrapeContentTypeMovie: case ScrapeContentTypeMovie:
return scraper.scrapeMovie(ctx, q) return scraper.scrapeMovie(ctx, q)
} }
return nil, ErrNotSupported return nil, ErrNotSupported
} }
func (s *jsonScraper) scrapeByName(ctx context.Context, name string, ty models.ScrapeContentType) ([]models.ScrapedContent, error) { func (s *jsonScraper) scrapeByName(ctx context.Context, name string, ty ScrapeContentType) ([]ScrapedContent, error) {
scraper := s.getJsonScraper() scraper := s.getJsonScraper()
if scraper == nil { if scraper == nil {
@ -121,9 +121,9 @@ func (s *jsonScraper) scrapeByName(ctx context.Context, name string, ty models.S
q := s.getJsonQuery(doc) q := s.getJsonQuery(doc)
q.setType(SearchQuery) q.setType(SearchQuery)
var content []models.ScrapedContent var content []ScrapedContent
switch ty { switch ty {
case models.ScrapeContentTypePerformer: case ScrapeContentTypePerformer:
performers, err := scraper.scrapePerformers(ctx, q) performers, err := scraper.scrapePerformers(ctx, q)
if err != nil { if err != nil {
return nil, err return nil, err
@ -134,7 +134,7 @@ func (s *jsonScraper) scrapeByName(ctx context.Context, name string, ty models.S
} }
return content, nil return content, nil
case models.ScrapeContentTypeScene: case ScrapeContentTypeScene:
scenes, err := scraper.scrapeScenes(ctx, q) scenes, err := scraper.scrapeScenes(ctx, q)
if err != nil { if err != nil {
return nil, err return nil, err
@ -150,7 +150,7 @@ func (s *jsonScraper) scrapeByName(ctx context.Context, name string, ty models.S
return nil, ErrNotSupported return nil, ErrNotSupported
} }
func (s *jsonScraper) scrapeSceneByScene(ctx context.Context, scene *models.Scene) (*models.ScrapedScene, error) { func (s *jsonScraper) scrapeSceneByScene(ctx context.Context, scene *models.Scene) (*ScrapedScene, error) {
// construct the URL // construct the URL
queryURL := queryURLParametersFromScene(scene) queryURL := queryURLParametersFromScene(scene)
if s.scraper.QueryURLReplacements != nil { if s.scraper.QueryURLReplacements != nil {
@ -174,7 +174,7 @@ func (s *jsonScraper) scrapeSceneByScene(ctx context.Context, scene *models.Scen
return scraper.scrapeScene(ctx, q) return scraper.scrapeScene(ctx, q)
} }
func (s *jsonScraper) scrapeByFragment(ctx context.Context, input Input) (models.ScrapedContent, error) { func (s *jsonScraper) scrapeByFragment(ctx context.Context, input Input) (ScrapedContent, error) {
switch { switch {
case input.Gallery != nil: case input.Gallery != nil:
return nil, fmt.Errorf("%w: cannot use a json scraper as a gallery fragment scraper", ErrNotSupported) return nil, fmt.Errorf("%w: cannot use a json scraper as a gallery fragment scraper", ErrNotSupported)
@ -209,7 +209,7 @@ func (s *jsonScraper) scrapeByFragment(ctx context.Context, input Input) (models
return scraper.scrapeScene(ctx, q) return scraper.scrapeScene(ctx, q)
} }
func (s *jsonScraper) scrapeGalleryByGallery(ctx context.Context, gallery *models.Gallery) (*models.ScrapedGallery, error) { func (s *jsonScraper) scrapeGalleryByGallery(ctx context.Context, gallery *models.Gallery) (*ScrapedGallery, error) {
// construct the URL // construct the URL
queryURL := queryURLParametersFromGallery(gallery) queryURL := queryURLParametersFromGallery(gallery)
if s.scraper.QueryURLReplacements != nil { if s.scraper.QueryURLReplacements != nil {

View file

@ -809,8 +809,8 @@ func (s mappedScraper) scrapePerformers(ctx context.Context, q mappedQuery) ([]*
return ret, nil return ret, nil
} }
func (s mappedScraper) processScene(ctx context.Context, q mappedQuery, r mappedResult) *models.ScrapedScene { func (s mappedScraper) processScene(ctx context.Context, q mappedQuery, r mappedResult) *ScrapedScene {
var ret models.ScrapedScene var ret ScrapedScene
sceneScraperConfig := s.Scene sceneScraperConfig := s.Scene
@ -884,8 +884,8 @@ func (s mappedScraper) processScene(ctx context.Context, q mappedQuery, r mapped
return &ret return &ret
} }
func (s mappedScraper) scrapeScenes(ctx context.Context, q mappedQuery) ([]*models.ScrapedScene, error) { func (s mappedScraper) scrapeScenes(ctx context.Context, q mappedQuery) ([]*ScrapedScene, error) {
var ret []*models.ScrapedScene var ret []*ScrapedScene
sceneScraperConfig := s.Scene sceneScraperConfig := s.Scene
sceneMap := sceneScraperConfig.mappedConfig sceneMap := sceneScraperConfig.mappedConfig
@ -903,8 +903,8 @@ func (s mappedScraper) scrapeScenes(ctx context.Context, q mappedQuery) ([]*mode
return ret, nil return ret, nil
} }
func (s mappedScraper) scrapeScene(ctx context.Context, q mappedQuery) (*models.ScrapedScene, error) { func (s mappedScraper) scrapeScene(ctx context.Context, q mappedQuery) (*ScrapedScene, error) {
var ret *models.ScrapedScene var ret *ScrapedScene
sceneScraperConfig := s.Scene sceneScraperConfig := s.Scene
sceneMap := sceneScraperConfig.mappedConfig sceneMap := sceneScraperConfig.mappedConfig
@ -921,8 +921,8 @@ func (s mappedScraper) scrapeScene(ctx context.Context, q mappedQuery) (*models.
return ret, nil return ret, nil
} }
func (s mappedScraper) scrapeGallery(ctx context.Context, q mappedQuery) (*models.ScrapedGallery, error) { func (s mappedScraper) scrapeGallery(ctx context.Context, q mappedQuery) (*ScrapedGallery, error) {
var ret *models.ScrapedGallery var ret *ScrapedGallery
galleryScraperConfig := s.Gallery galleryScraperConfig := s.Gallery
galleryMap := galleryScraperConfig.mappedConfig galleryMap := galleryScraperConfig.mappedConfig
@ -937,7 +937,7 @@ func (s mappedScraper) scrapeGallery(ctx context.Context, q mappedQuery) (*model
logger.Debug(`Processing gallery:`) logger.Debug(`Processing gallery:`)
results := galleryMap.process(ctx, q, s.Common) results := galleryMap.process(ctx, q, s.Common)
if len(results) > 0 { if len(results) > 0 {
ret = &models.ScrapedGallery{} ret = &ScrapedGallery{}
results[0].apply(ret) results[0].apply(ret)

12
pkg/scraper/movie.go Normal file
View file

@ -0,0 +1,12 @@
package scraper
type ScrapedMovieInput struct {
Name *string `json:"name"`
Aliases *string `json:"aliases"`
Duration *string `json:"duration"`
Date *string `json:"date"`
Rating *string `json:"rating"`
Director *string `json:"director"`
URL *string `json:"url"`
Synopsis *string `json:"synopsis"`
}

27
pkg/scraper/performer.go Normal file
View file

@ -0,0 +1,27 @@
package scraper
type ScrapedPerformerInput struct {
// Set if performer matched
StoredID *string `json:"stored_id"`
Name *string `json:"name"`
Gender *string `json:"gender"`
URL *string `json:"url"`
Twitter *string `json:"twitter"`
Instagram *string `json:"instagram"`
Birthdate *string `json:"birthdate"`
Ethnicity *string `json:"ethnicity"`
Country *string `json:"country"`
EyeColor *string `json:"eye_color"`
Height *string `json:"height"`
Measurements *string `json:"measurements"`
FakeTits *string `json:"fake_tits"`
CareerLength *string `json:"career_length"`
Tattoos *string `json:"tattoos"`
Piercings *string `json:"piercings"`
Aliases *string `json:"aliases"`
Details *string `json:"details"`
DeathDate *string `json:"death_date"`
HairColor *string `json:"hair_color"`
Weight *string `json:"weight"`
RemoteSiteID *string `json:"remote_site_id"`
}

View file

@ -11,7 +11,7 @@ import (
// postScrape handles post-processing of scraped content. If the content // postScrape handles post-processing of scraped content. If the content
// requires post-processing, this function fans out to the given content // requires post-processing, this function fans out to the given content
// type and post-processes it. // type and post-processes it.
func (c Cache) postScrape(ctx context.Context, content models.ScrapedContent) (models.ScrapedContent, error) { func (c Cache) postScrape(ctx context.Context, content ScrapedContent) (ScrapedContent, error) {
// Analyze the concrete type, call the right post-processing function // Analyze the concrete type, call the right post-processing function
switch v := content.(type) { switch v := content.(type) {
case *models.ScrapedPerformer: case *models.ScrapedPerformer:
@ -20,17 +20,17 @@ func (c Cache) postScrape(ctx context.Context, content models.ScrapedContent) (m
} }
case models.ScrapedPerformer: case models.ScrapedPerformer:
return c.postScrapePerformer(ctx, v) return c.postScrapePerformer(ctx, v)
case *models.ScrapedScene: case *ScrapedScene:
if v != nil { if v != nil {
return c.postScrapeScene(ctx, *v) return c.postScrapeScene(ctx, *v)
} }
case models.ScrapedScene: case ScrapedScene:
return c.postScrapeScene(ctx, v) return c.postScrapeScene(ctx, v)
case *models.ScrapedGallery: case *ScrapedGallery:
if v != nil { if v != nil {
return c.postScrapeGallery(ctx, *v) return c.postScrapeGallery(ctx, *v)
} }
case models.ScrapedGallery: case ScrapedGallery:
return c.postScrapeGallery(ctx, v) return c.postScrapeGallery(ctx, v)
case *models.ScrapedMovie: case *models.ScrapedMovie:
if v != nil { if v != nil {
@ -44,7 +44,7 @@ func (c Cache) postScrape(ctx context.Context, content models.ScrapedContent) (m
return content, nil return content, nil
} }
func (c Cache) postScrapePerformer(ctx context.Context, p models.ScrapedPerformer) (models.ScrapedContent, error) { func (c Cache) postScrapePerformer(ctx context.Context, p models.ScrapedPerformer) (ScrapedContent, error) {
if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error { if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error {
tqb := r.Tag() tqb := r.Tag()
@ -67,7 +67,7 @@ func (c Cache) postScrapePerformer(ctx context.Context, p models.ScrapedPerforme
return p, nil return p, nil
} }
func (c Cache) postScrapeMovie(ctx context.Context, m models.ScrapedMovie) (models.ScrapedContent, error) { func (c Cache) postScrapeMovie(ctx context.Context, m models.ScrapedMovie) (ScrapedContent, error) {
if m.Studio != nil { if m.Studio != nil {
if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error { if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error {
return match.ScrapedStudio(r.Studio(), m.Studio, nil) return match.ScrapedStudio(r.Studio(), m.Studio, nil)
@ -105,7 +105,7 @@ func (c Cache) postScrapeScenePerformer(ctx context.Context, p models.ScrapedPer
return nil return nil
} }
func (c Cache) postScrapeScene(ctx context.Context, scene models.ScrapedScene) (models.ScrapedContent, error) { func (c Cache) postScrapeScene(ctx context.Context, scene ScrapedScene) (ScrapedContent, error) {
if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error { if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error {
pqb := r.Performer() pqb := r.Performer()
mqb := r.Movie() mqb := r.Movie()
@ -159,7 +159,7 @@ func (c Cache) postScrapeScene(ctx context.Context, scene models.ScrapedScene) (
return scene, nil return scene, nil
} }
func (c Cache) postScrapeGallery(ctx context.Context, g models.ScrapedGallery) (models.ScrapedContent, error) { func (c Cache) postScrapeGallery(ctx context.Context, g ScrapedGallery) (ScrapedContent, error) {
if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error { if err := c.txnManager.WithReadTxn(ctx, func(r models.ReaderRepository) error {
pqb := r.Performer() pqb := r.Performer()
tqb := r.Tag() tqb := r.Tag()

Some files were not shown because too many files have changed in this diff Show more