mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Support multiple content folders. Closes #2
This commit is contained in:
parent
d959d61e6c
commit
ae9bbf237f
17 changed files with 1449 additions and 886 deletions
10
README.md
10
README.md
|
|
@ -15,6 +15,10 @@ Simply run the executable (double click the exe on windows or run `./stash-osx`
|
|||
|
||||
*Note for Windows users:* Running the app might present a security prompt since the binary isn't signed yet. Just click more info and then the run anyway button.
|
||||
|
||||
## Slack
|
||||
|
||||
I created a Slack channel to discuss the project. [Click here to join.](https://join.slack.com/stash-project/shared_invite/MTc2Nzg0NjAyNzg4LTE0OTM1ODU4MTgtNDcwODRiMGIwYQ)
|
||||
|
||||
#### FFMPEG
|
||||
|
||||
If stash is unable to find or download FFMPEG then download it yourself from the link for your platform:
|
||||
|
|
@ -27,9 +31,7 @@ The `ffmpeg(.exe)` and `ffprobe(.exe)` files should be placed in `~/.stash` on m
|
|||
|
||||
# FAQ
|
||||
|
||||
> Does stash support multiple folders?
|
||||
|
||||
Not yet, but this will come in the future.
|
||||
TODO
|
||||
|
||||
# Development
|
||||
|
||||
|
|
@ -66,7 +68,7 @@ TODO
|
|||
|
||||
## Cross compiling
|
||||
|
||||
This project makes use of [this](https://github.com/bep/dockerfiles/tree/master/ci-goreleaser) docker container to create an environment
|
||||
This project uses a modification of [this](https://github.com/bep/dockerfiles/tree/master/ci-goreleaser) docker container to create an environment
|
||||
where the app can be cross compiled. This process is kicked off by CI via the `scripts/cross-compile.sh` script. Run the following
|
||||
command to open a bash shell to the container to poke around:
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
func (r *queryResolver) ConfigureGeneral(ctx context.Context, input *models.ConfigGeneralInput) (models.ConfigGeneralResult, error) {
|
||||
func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input *models.ConfigGeneralInput) (models.ConfigGeneralResult, error) {
|
||||
if input == nil {
|
||||
return makeConfigGeneralResult(), fmt.Errorf("nil input")
|
||||
}
|
||||
|
|
@ -29,9 +29,3 @@ func (r *queryResolver) ConfigureGeneral(ctx context.Context, input *models.Conf
|
|||
|
||||
return makeConfigGeneralResult(), nil
|
||||
}
|
||||
|
||||
func makeConfigGeneralResult() models.ConfigGeneralResult {
|
||||
return models.ConfigGeneralResult{
|
||||
Stashes: config.GetStashPaths(),
|
||||
}
|
||||
}
|
||||
32
pkg/api/resolver_query_configuration.go
Normal file
32
pkg/api/resolver_query_configuration.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
func (r *queryResolver) Configuration(ctx context.Context) (models.ConfigResult, error) {
|
||||
return makeConfigResult(), nil
|
||||
}
|
||||
|
||||
func (r *queryResolver) Directories(ctx context.Context, path *string) ([]string, error) {
|
||||
var dirPath = ""
|
||||
if path != nil {
|
||||
dirPath = *path
|
||||
}
|
||||
return utils.ListDir(dirPath), nil
|
||||
}
|
||||
|
||||
func makeConfigResult() models.ConfigResult {
|
||||
return models.ConfigResult{
|
||||
General: makeConfigGeneralResult(),
|
||||
}
|
||||
}
|
||||
|
||||
func makeConfigGeneralResult() models.ConfigGeneralResult {
|
||||
return models.ConfigGeneralResult{
|
||||
Stashes: config.GetStashPaths(),
|
||||
}
|
||||
}
|
||||
|
|
@ -54,13 +54,6 @@ func initConfig() {
|
|||
viper.AddConfigPath("$HOME/.stash") // Look for the config in the home directory
|
||||
viper.AddConfigPath(".") // Look for config in the working directory
|
||||
|
||||
viper.SetDefault(config.Database, paths.GetDefaultDatabaseFilePath())
|
||||
|
||||
// Set generated to the metadata path for backwards compat
|
||||
if !viper.IsSet(config.Generated) {
|
||||
viper.SetDefault(config.Generated, viper.GetString(config.Metadata))
|
||||
}
|
||||
|
||||
err := viper.ReadInConfig() // Find and read the config file
|
||||
if err != nil { // Handle errors reading the config file
|
||||
_ = utils.Touch(paths.GetDefaultConfigFilePath())
|
||||
|
|
@ -69,6 +62,11 @@ func initConfig() {
|
|||
}
|
||||
}
|
||||
|
||||
viper.SetDefault(config.Database, paths.GetDefaultDatabaseFilePath())
|
||||
|
||||
// Set generated to the metadata path for backwards compat
|
||||
viper.SetDefault(config.Generated, viper.GetString(config.Metadata))
|
||||
|
||||
// Watch for changes
|
||||
viper.WatchConfig()
|
||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package paths
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
|
@ -25,16 +25,8 @@ func NewPaths() *Paths {
|
|||
return &p
|
||||
}
|
||||
|
||||
func GetHomeDirectory() string {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return currentUser.HomeDir
|
||||
}
|
||||
|
||||
func GetConfigDirectory() string {
|
||||
return filepath.Join(GetHomeDirectory(), ".stash")
|
||||
return filepath.Join(utils.GetHomeDirectory(), ".stash")
|
||||
}
|
||||
|
||||
func GetDefaultDatabaseFilePath() string {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ type ComplexityRoot struct {
|
|||
Stashes func(childComplexity int) int
|
||||
}
|
||||
|
||||
ConfigResult struct {
|
||||
General func(childComplexity int) int
|
||||
}
|
||||
|
||||
FindGalleriesResultType struct {
|
||||
Count func(childComplexity int) int
|
||||
Galleries func(childComplexity int) int
|
||||
|
|
@ -108,6 +112,7 @@ type ComplexityRoot struct {
|
|||
TagCreate func(childComplexity int, input TagCreateInput) int
|
||||
TagUpdate func(childComplexity int, input TagUpdateInput) int
|
||||
TagDestroy func(childComplexity int, input TagDestroyInput) int
|
||||
ConfigureGeneral func(childComplexity int, input *ConfigGeneralInput) int
|
||||
}
|
||||
|
||||
Performer struct {
|
||||
|
|
@ -153,7 +158,8 @@ type ComplexityRoot struct {
|
|||
SceneMarkerTags func(childComplexity int, scene_id string) int
|
||||
ScrapeFreeones func(childComplexity int, performer_name string) int
|
||||
ScrapeFreeonesPerformerList func(childComplexity int, query string) int
|
||||
ConfigureGeneral func(childComplexity int, input *ConfigGeneralInput) int
|
||||
Configuration func(childComplexity int) int
|
||||
Directories func(childComplexity int, path *string) int
|
||||
MetadataImport func(childComplexity int) int
|
||||
MetadataExport func(childComplexity int) int
|
||||
MetadataScan func(childComplexity int) int
|
||||
|
|
@ -284,6 +290,7 @@ type MutationResolver interface {
|
|||
TagCreate(ctx context.Context, input TagCreateInput) (*Tag, error)
|
||||
TagUpdate(ctx context.Context, input TagUpdateInput) (*Tag, error)
|
||||
TagDestroy(ctx context.Context, input TagDestroyInput) (bool, error)
|
||||
ConfigureGeneral(ctx context.Context, input *ConfigGeneralInput) (ConfigGeneralResult, error)
|
||||
}
|
||||
type PerformerResolver interface {
|
||||
ID(ctx context.Context, obj *Performer) (string, error)
|
||||
|
|
@ -327,7 +334,8 @@ type QueryResolver interface {
|
|||
SceneMarkerTags(ctx context.Context, scene_id string) ([]SceneMarkerTag, error)
|
||||
ScrapeFreeones(ctx context.Context, performer_name string) (*ScrapedPerformer, error)
|
||||
ScrapeFreeonesPerformerList(ctx context.Context, query string) ([]string, error)
|
||||
ConfigureGeneral(ctx context.Context, input *ConfigGeneralInput) (ConfigGeneralResult, error)
|
||||
Configuration(ctx context.Context) (ConfigResult, error)
|
||||
Directories(ctx context.Context, path *string) ([]string, error)
|
||||
MetadataImport(ctx context.Context) (string, error)
|
||||
MetadataExport(ctx context.Context) (string, error)
|
||||
MetadataScan(ctx context.Context) (string, error)
|
||||
|
|
@ -607,6 +615,34 @@ func (e *executableSchema) field_Mutation_tagDestroy_args(ctx context.Context, r
|
|||
|
||||
}
|
||||
|
||||
func (e *executableSchema) field_Mutation_configureGeneral_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
args := map[string]interface{}{}
|
||||
var arg0 *ConfigGeneralInput
|
||||
if tmp, ok := rawArgs["input"]; ok {
|
||||
var err error
|
||||
var ptr1 ConfigGeneralInput
|
||||
if tmp != nil {
|
||||
ptr1, err = UnmarshalConfigGeneralInput(tmp)
|
||||
arg0 = &ptr1
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if arg0 != nil {
|
||||
var err error
|
||||
arg0, err = e.ConfigGeneralInputMiddleware(ctx, arg0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
|
||||
}
|
||||
|
||||
func (e *executableSchema) field_Query_findScene_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
args := map[string]interface{}{}
|
||||
var arg0 *string
|
||||
|
|
@ -1066,30 +1102,22 @@ func (e *executableSchema) field_Query_scrapeFreeonesPerformerList_args(ctx cont
|
|||
|
||||
}
|
||||
|
||||
func (e *executableSchema) field_Query_configureGeneral_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
func (e *executableSchema) field_Query_directories_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
args := map[string]interface{}{}
|
||||
var arg0 *ConfigGeneralInput
|
||||
if tmp, ok := rawArgs["input"]; ok {
|
||||
var arg0 *string
|
||||
if tmp, ok := rawArgs["path"]; ok {
|
||||
var err error
|
||||
var ptr1 ConfigGeneralInput
|
||||
var ptr1 string
|
||||
if tmp != nil {
|
||||
ptr1, err = UnmarshalConfigGeneralInput(tmp)
|
||||
ptr1, err = graphql.UnmarshalString(tmp)
|
||||
arg0 = &ptr1
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if arg0 != nil {
|
||||
var err error
|
||||
arg0, err = e.ConfigGeneralInputMiddleware(ctx, arg0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
args["input"] = arg0
|
||||
args["path"] = arg0
|
||||
return args, nil
|
||||
|
||||
}
|
||||
|
|
@ -1159,6 +1187,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.ConfigGeneralResult.Stashes(childComplexity), true
|
||||
|
||||
case "ConfigResult.general":
|
||||
if e.complexity.ConfigResult.General == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.ConfigResult.General(childComplexity), true
|
||||
|
||||
case "FindGalleriesResultType.count":
|
||||
if e.complexity.FindGalleriesResultType.Count == nil {
|
||||
break
|
||||
|
|
@ -1438,6 +1473,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Mutation.TagDestroy(childComplexity, args["input"].(TagDestroyInput)), true
|
||||
|
||||
case "Mutation.configureGeneral":
|
||||
if e.complexity.Mutation.ConfigureGeneral == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := e.field_Mutation_configureGeneral_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.ConfigureGeneral(childComplexity, args["input"].(*ConfigGeneralInput)), true
|
||||
|
||||
case "Performer.id":
|
||||
if e.complexity.Performer.Id == nil {
|
||||
break
|
||||
|
|
@ -1796,17 +1843,24 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.Query.ScrapeFreeonesPerformerList(childComplexity, args["query"].(string)), true
|
||||
|
||||
case "Query.configureGeneral":
|
||||
if e.complexity.Query.ConfigureGeneral == nil {
|
||||
case "Query.configuration":
|
||||
if e.complexity.Query.Configuration == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := e.field_Query_configureGeneral_args(context.TODO(), rawArgs)
|
||||
return e.complexity.Query.Configuration(childComplexity), true
|
||||
|
||||
case "Query.directories":
|
||||
if e.complexity.Query.Directories == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := e.field_Query_directories_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Query.ConfigureGeneral(childComplexity, args["input"].(*ConfigGeneralInput)), true
|
||||
return e.complexity.Query.Directories(childComplexity, args["path"].(*string)), true
|
||||
|
||||
case "Query.metadataImport":
|
||||
if e.complexity.Query.MetadataImport == nil {
|
||||
|
|
@ -2494,6 +2548,62 @@ func (ec *executionContext) _ConfigGeneralResult_stashes(ctx context.Context, fi
|
|||
return arr1
|
||||
}
|
||||
|
||||
var configResultImplementors = []string{"ConfigResult"}
|
||||
|
||||
// nolint: gocyclo, errcheck, gas, goconst
|
||||
func (ec *executionContext) _ConfigResult(ctx context.Context, sel ast.SelectionSet, obj *ConfigResult) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ctx, sel, configResultImplementors)
|
||||
|
||||
out := graphql.NewFieldSet(fields)
|
||||
invalid := false
|
||||
for i, field := range fields {
|
||||
switch field.Name {
|
||||
case "__typename":
|
||||
out.Values[i] = graphql.MarshalString("ConfigResult")
|
||||
case "general":
|
||||
out.Values[i] = ec._ConfigResult_general(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalid = true
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
}
|
||||
out.Dispatch()
|
||||
if invalid {
|
||||
return graphql.Null
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// nolint: vetshadow
|
||||
func (ec *executionContext) _ConfigResult_general(ctx context.Context, field graphql.CollectedField, obj *ConfigResult) graphql.Marshaler {
|
||||
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
|
||||
rctx := &graphql.ResolverContext{
|
||||
Object: "ConfigResult",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
}
|
||||
ctx = graphql.WithResolverContext(ctx, rctx)
|
||||
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
|
||||
resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.General, nil
|
||||
})
|
||||
if resTmp == nil {
|
||||
if !ec.HasError(rctx) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(ConfigGeneralResult)
|
||||
rctx.Result = res
|
||||
ctx = ec.Tracer.StartFieldChildExecution(ctx)
|
||||
|
||||
return ec._ConfigGeneralResult(ctx, field.Selections, &res)
|
||||
}
|
||||
|
||||
var findGalleriesResultTypeImplementors = []string{"FindGalleriesResultType"}
|
||||
|
||||
// nolint: gocyclo, errcheck, gas, goconst
|
||||
|
|
@ -3598,6 +3708,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
|||
if out.Values[i] == graphql.Null {
|
||||
invalid = true
|
||||
}
|
||||
case "configureGeneral":
|
||||
out.Values[i] = ec._Mutation_configureGeneral(ctx, field)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalid = true
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
|
@ -4001,6 +4116,41 @@ func (ec *executionContext) _Mutation_tagDestroy(ctx context.Context, field grap
|
|||
return graphql.MarshalBoolean(res)
|
||||
}
|
||||
|
||||
// nolint: vetshadow
|
||||
func (ec *executionContext) _Mutation_configureGeneral(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
|
||||
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
|
||||
rctx := &graphql.ResolverContext{
|
||||
Object: "Mutation",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
}
|
||||
ctx = graphql.WithResolverContext(ctx, rctx)
|
||||
rawArgs := field.ArgumentMap(ec.Variables)
|
||||
args, err := ec.field_Mutation_configureGeneral_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
rctx.Args = args
|
||||
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
|
||||
resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Mutation().ConfigureGeneral(rctx, args["input"].(*ConfigGeneralInput))
|
||||
})
|
||||
if resTmp == nil {
|
||||
if !ec.HasError(rctx) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(ConfigGeneralResult)
|
||||
rctx.Result = res
|
||||
ctx = ec.Tracer.StartFieldChildExecution(ctx)
|
||||
|
||||
return ec._ConfigGeneralResult(ctx, field.Selections, &res)
|
||||
}
|
||||
|
||||
var performerImplementors = []string{"Performer"}
|
||||
|
||||
// nolint: gocyclo, errcheck, gas, goconst
|
||||
|
|
@ -4935,10 +5085,19 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
|
|||
}
|
||||
return res
|
||||
})
|
||||
case "configureGeneral":
|
||||
case "configuration":
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
res = ec._Query_configureGeneral(ctx, field)
|
||||
res = ec._Query_configuration(ctx, field)
|
||||
if res == graphql.Null {
|
||||
invalid = true
|
||||
}
|
||||
return res
|
||||
})
|
||||
case "directories":
|
||||
field := field
|
||||
out.Concurrently(i, func() (res graphql.Marshaler) {
|
||||
res = ec._Query_directories(ctx, field)
|
||||
if res == graphql.Null {
|
||||
invalid = true
|
||||
}
|
||||
|
|
@ -5833,7 +5992,35 @@ func (ec *executionContext) _Query_scrapeFreeonesPerformerList(ctx context.Conte
|
|||
}
|
||||
|
||||
// nolint: vetshadow
|
||||
func (ec *executionContext) _Query_configureGeneral(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
|
||||
func (ec *executionContext) _Query_configuration(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
|
||||
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
|
||||
rctx := &graphql.ResolverContext{
|
||||
Object: "Query",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
}
|
||||
ctx = graphql.WithResolverContext(ctx, rctx)
|
||||
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
|
||||
resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Query().Configuration(rctx)
|
||||
})
|
||||
if resTmp == nil {
|
||||
if !ec.HasError(rctx) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(ConfigResult)
|
||||
rctx.Result = res
|
||||
ctx = ec.Tracer.StartFieldChildExecution(ctx)
|
||||
|
||||
return ec._ConfigResult(ctx, field.Selections, &res)
|
||||
}
|
||||
|
||||
// nolint: vetshadow
|
||||
func (ec *executionContext) _Query_directories(ctx context.Context, field graphql.CollectedField) graphql.Marshaler {
|
||||
ctx = ec.Tracer.StartFieldExecution(ctx, field)
|
||||
defer func() { ec.Tracer.EndFieldExecution(ctx) }()
|
||||
rctx := &graphql.ResolverContext{
|
||||
|
|
@ -5843,7 +6030,7 @@ func (ec *executionContext) _Query_configureGeneral(ctx context.Context, field g
|
|||
}
|
||||
ctx = graphql.WithResolverContext(ctx, rctx)
|
||||
rawArgs := field.ArgumentMap(ec.Variables)
|
||||
args, err := ec.field_Query_configureGeneral_args(ctx, rawArgs)
|
||||
args, err := ec.field_Query_directories_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
|
|
@ -5852,7 +6039,7 @@ func (ec *executionContext) _Query_configureGeneral(ctx context.Context, field g
|
|||
ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
|
||||
resTmp := ec.FieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Query().ConfigureGeneral(rctx, args["input"].(*ConfigGeneralInput))
|
||||
return ec.resolvers.Query().Directories(rctx, args["path"].(*string))
|
||||
})
|
||||
if resTmp == nil {
|
||||
if !ec.HasError(rctx) {
|
||||
|
|
@ -5860,11 +6047,19 @@ func (ec *executionContext) _Query_configureGeneral(ctx context.Context, field g
|
|||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(ConfigGeneralResult)
|
||||
res := resTmp.([]string)
|
||||
rctx.Result = res
|
||||
ctx = ec.Tracer.StartFieldChildExecution(ctx)
|
||||
|
||||
return ec._ConfigGeneralResult(ctx, field.Selections, &res)
|
||||
arr1 := make(graphql.Array, len(res))
|
||||
|
||||
for idx1 := range res {
|
||||
arr1[idx1] = func() graphql.Marshaler {
|
||||
return graphql.MarshalString(res[idx1])
|
||||
}()
|
||||
}
|
||||
|
||||
return arr1
|
||||
}
|
||||
|
||||
// nolint: vetshadow
|
||||
|
|
@ -11960,6 +12155,11 @@ type ConfigGeneralResult {
|
|||
stashes: [String!]
|
||||
}
|
||||
|
||||
"""All configuration settings"""
|
||||
type ConfigResult {
|
||||
general: ConfigGeneralResult!
|
||||
}
|
||||
|
||||
#############
|
||||
# Root Schema
|
||||
#############
|
||||
|
|
@ -12011,7 +12211,10 @@ type Query {
|
|||
scrapeFreeonesPerformerList(query: String!): [String!]!
|
||||
|
||||
# Config
|
||||
configureGeneral(input: ConfigGeneralInput): ConfigGeneralResult!
|
||||
"""Returns the current, complete configuration"""
|
||||
configuration: ConfigResult!
|
||||
"""Returns an array of paths for the given path"""
|
||||
directories(path: String): [String!]!
|
||||
|
||||
# Metadata
|
||||
|
||||
|
|
@ -12049,10 +12252,13 @@ type Mutation {
|
|||
tagCreate(input: TagCreateInput!): Tag
|
||||
tagUpdate(input: TagUpdateInput!): Tag
|
||||
tagDestroy(input: TagDestroyInput!): Boolean!
|
||||
|
||||
"""Change general configuration options"""
|
||||
configureGeneral(input: ConfigGeneralInput): ConfigGeneralResult!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
"""Update from the meatadata manager"""
|
||||
"""Update from the metadata manager"""
|
||||
metadataUpdate: String!
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ type ConfigGeneralResult struct {
|
|||
Stashes []string `json:"stashes"`
|
||||
}
|
||||
|
||||
// All configuration settings
|
||||
type ConfigResult struct {
|
||||
General ConfigGeneralResult `json:"general"`
|
||||
}
|
||||
|
||||
type FindFilterType struct {
|
||||
Q *string `json:"q"`
|
||||
Page *int `json:"page"`
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import (
|
|||
"fmt"
|
||||
"github.com/h2non/filetype"
|
||||
"github.com/h2non/filetype/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
|
|
@ -84,3 +86,41 @@ func EmptyDir(path string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ListDir(path string) []string {
|
||||
if path == "" {
|
||||
path = GetHomeDirectory()
|
||||
}
|
||||
|
||||
absolutePath, err := filepath.Abs(path)
|
||||
if err == nil {
|
||||
path = absolutePath
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
path = filepath.Dir(path)
|
||||
files, err = ioutil.ReadDir(path)
|
||||
}
|
||||
|
||||
var dirPaths []string
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
continue
|
||||
}
|
||||
abs, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
dirPaths = append(dirPaths, filepath.Join(abs, file.Name()))
|
||||
}
|
||||
return dirPaths
|
||||
}
|
||||
|
||||
func GetHomeDirectory() string {
|
||||
currentUser, err := user.Current()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return currentUser.HomeDir
|
||||
}
|
||||
|
|
|
|||
9
schema/documents/data/config.graphql
Normal file
9
schema/documents/data/config.graphql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
fragment ConfigGeneralData on ConfigGeneralResult {
|
||||
stashes
|
||||
}
|
||||
|
||||
fragment ConfigData on ConfigResult {
|
||||
general {
|
||||
...ConfigGeneralData
|
||||
}
|
||||
}
|
||||
5
schema/documents/mutations/config.graphql
Normal file
5
schema/documents/mutations/config.graphql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mutation ConfigureGeneral($input: ConfigGeneralInput!) {
|
||||
configureGeneral(input: $input) {
|
||||
...ConfigGeneralData
|
||||
}
|
||||
}
|
||||
9
schema/documents/queries/settings/config.graphql
Normal file
9
schema/documents/queries/settings/config.graphql
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
query Configuration {
|
||||
configuration {
|
||||
...ConfigData
|
||||
}
|
||||
}
|
||||
|
||||
query Directories($path: String) {
|
||||
directories(path: $path)
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
query ConfigureGeneral($input: ConfigGeneralInput!) {
|
||||
configureGeneral(input: $input)
|
||||
}
|
||||
|
|
@ -386,6 +386,11 @@ type ConfigGeneralResult {
|
|||
stashes: [String!]
|
||||
}
|
||||
|
||||
"""All configuration settings"""
|
||||
type ConfigResult {
|
||||
general: ConfigGeneralResult!
|
||||
}
|
||||
|
||||
#############
|
||||
# Root Schema
|
||||
#############
|
||||
|
|
@ -437,7 +442,10 @@ type Query {
|
|||
scrapeFreeonesPerformerList(query: String!): [String!]!
|
||||
|
||||
# Config
|
||||
configureGeneral(input: ConfigGeneralInput): ConfigGeneralResult!
|
||||
"""Returns the current, complete configuration"""
|
||||
configuration: ConfigResult!
|
||||
"""Returns an array of paths for the given path"""
|
||||
directories(path: String): [String!]!
|
||||
|
||||
# Metadata
|
||||
|
||||
|
|
@ -475,10 +483,13 @@ type Mutation {
|
|||
tagCreate(input: TagCreateInput!): Tag
|
||||
tagUpdate(input: TagUpdateInput!): Tag
|
||||
tagDestroy(input: TagDestroyInput!): Boolean!
|
||||
|
||||
"""Change general configuration options"""
|
||||
configureGeneral(input: ConfigGeneralInput): ConfigGeneralResult!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
"""Update from the meatadata manager"""
|
||||
"""Update from the metadata manager"""
|
||||
metadataUpdate: String!
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,73 @@
|
|||
import {
|
||||
Button,
|
||||
Divider,
|
||||
FormGroup,
|
||||
H1,
|
||||
H4,
|
||||
H6,
|
||||
Spinner,
|
||||
Tag,
|
||||
} from "@blueprintjs/core";
|
||||
import React, { FunctionComponent } from "react";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import * as GQL from "../../core/generated-graphql";
|
||||
import { StashService } from "../../core/StashService";
|
||||
import { ErrorUtils } from "../../utils/errors";
|
||||
import { TextUtils } from "../../utils/text";
|
||||
import { ToastUtils } from "../../utils/toasts";
|
||||
import { FolderSelect } from "../Shared/FolderSelect/FolderSelect";
|
||||
|
||||
interface IProps {}
|
||||
|
||||
export const SettingsConfigurationPanel: FunctionComponent<IProps> = (props: IProps) => {
|
||||
// Editing config state
|
||||
const [stashes, setStashes] = useState<string[]>([]);
|
||||
|
||||
// const [config, setConfig] = useState<Partial<GQL.ConfigDataFragment>>({});
|
||||
const { data, error, loading } = StashService.useConfiguration();
|
||||
|
||||
const updateGeneralConfig = StashService.useConfigureGeneral({
|
||||
stashes,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || !data.configuration || !!error) { return; }
|
||||
const conf = StashService.nullToUndefined(data.configuration) as GQL.ConfigDataFragment;
|
||||
if (!!conf.general) {
|
||||
setStashes(conf.general.stashes || []);
|
||||
}
|
||||
// setConfig(conf);
|
||||
}, [data]);
|
||||
|
||||
function onStashesChanged(directories: string[]) {
|
||||
setStashes(directories);
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
try {
|
||||
const result = await updateGeneralConfig();
|
||||
console.log(result);
|
||||
ToastUtils.success("Updated config");
|
||||
} catch (e) {
|
||||
ErrorUtils.handle(e);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
Configuration
|
||||
{!!error ? error : undefined}
|
||||
{(!data || !data.configuration || loading) ? <Spinner size={Spinner.SIZE_LARGE} /> : undefined}
|
||||
<H4>Library</H4>
|
||||
<FormGroup
|
||||
label="Stashes"
|
||||
helperText="Directory locations to your content"
|
||||
>
|
||||
<FolderSelect
|
||||
directories={stashes}
|
||||
onDirectoriesChanged={onStashesChanged}
|
||||
/>
|
||||
</FormGroup>
|
||||
<Divider />
|
||||
<Button intent="primary" onClick={() => onSave()}>Save</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
85
ui/v2/src/components/Shared/FolderSelect/FolderSelect.tsx
Normal file
85
ui/v2/src/components/Shared/FolderSelect/FolderSelect.tsx
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import {
|
||||
Button,
|
||||
Classes,
|
||||
Dialog,
|
||||
InputGroup,
|
||||
Spinner,
|
||||
} from "@blueprintjs/core";
|
||||
import _ from "lodash";
|
||||
import React, { FunctionComponent, useEffect, useState } from "react";
|
||||
import { StashService } from "../../../core/StashService";
|
||||
|
||||
interface IProps {
|
||||
directories: string[];
|
||||
onDirectoriesChanged: (directories: string[]) => void;
|
||||
}
|
||||
|
||||
export const FolderSelect: FunctionComponent<IProps> = (props: IProps) => {
|
||||
const [currentDirectory, setCurrentDirectory] = useState<string>("");
|
||||
const [isDisplayingDialog, setIsDisplayingDialog] = useState<boolean>(false);
|
||||
const [selectableDirectories, setSelectableDirectories] = useState<string[]>([]);
|
||||
const [selectedDirectories, setSelectedDirectories] = useState<string[]>([]);
|
||||
const { data, error, loading } = StashService.useDirectories(currentDirectory);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedDirectories(props.directories);
|
||||
}, [props.directories]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || !data.directories || !!error) { return; }
|
||||
setSelectableDirectories(StashService.nullToUndefined(data.directories));
|
||||
}, [data]);
|
||||
|
||||
function onSelectDirectory() {
|
||||
selectedDirectories.push(currentDirectory);
|
||||
setSelectedDirectories(selectedDirectories);
|
||||
setCurrentDirectory("");
|
||||
setIsDisplayingDialog(false);
|
||||
props.onDirectoriesChanged(selectedDirectories);
|
||||
}
|
||||
|
||||
function onRemoveDirectory(directory: string) {
|
||||
const newSelectedDirectories = selectedDirectories.filter((dir) => dir !== directory);
|
||||
setSelectedDirectories(newSelectedDirectories);
|
||||
props.onDirectoriesChanged(newSelectedDirectories);
|
||||
}
|
||||
|
||||
function renderDialog() {
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={isDisplayingDialog}
|
||||
onClose={() => setIsDisplayingDialog(false)}
|
||||
title="Select Directory"
|
||||
>
|
||||
<div className="dialog-content">
|
||||
<InputGroup
|
||||
large={true}
|
||||
placeholder="File path"
|
||||
onChange={(e: any) => setCurrentDirectory(e.target.value)}
|
||||
value={currentDirectory}
|
||||
rightElement={(!data || !data.directories || loading) ? <Spinner size={Spinner.SIZE_SMALL} /> : undefined}
|
||||
/>
|
||||
{selectableDirectories.map((path) => {
|
||||
return <div key={path} onClick={() => setCurrentDirectory(path)}>{path}</div>;
|
||||
})}
|
||||
</div>
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button onClick={() => onSelectDirectory()}>Add</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!!error ? error : undefined}
|
||||
{renderDialog()}
|
||||
{selectedDirectories.map((path) => {
|
||||
return <div key={path}>{path} <a onClick={() => onRemoveDirectory(path)}>Remove</a></div>;
|
||||
})}
|
||||
<Button small={true} onClick={() => setIsDisplayingDialog(true)}>Add Directory</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -118,6 +118,9 @@ export class StashService {
|
|||
}
|
||||
public static useStats() { return GQL.useStats(); }
|
||||
|
||||
public static useConfiguration() { return GQL.useConfiguration(); }
|
||||
public static useDirectories(path?: string) { return GQL.useDirectories({ variables: { path }}); }
|
||||
|
||||
public static usePerformerCreate(input: GQL.PerformerCreateInput) {
|
||||
return GQL.usePerformerCreate({ variables: input });
|
||||
}
|
||||
|
|
@ -143,6 +146,10 @@ export class StashService {
|
|||
return GQL.useTagUpdate({ variables: input, refetchQueries: ["AllTags"] });
|
||||
}
|
||||
|
||||
public static useConfigureGeneral(input: GQL.ConfigGeneralInput) {
|
||||
return GQL.useConfigureGeneral({ variables: { input }, refetchQueries: ["Configuration"] });
|
||||
}
|
||||
|
||||
public static queryScrapeFreeones(performerName: string) {
|
||||
return StashService.client.query<GQL.ScrapeFreeonesQuery>({
|
||||
query: GQL.ScrapeFreeonesDocument,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue