From 92bb67959bb52106c8acf8a02a8c9fede8ccf5ec Mon Sep 17 00:00:00 2001 From: royalcat Date: Tue, 17 Dec 2024 01:04:44 +0300 Subject: [PATCH] torrent source filter --- graphql/sources/qbittorrent_query.graphql | 6 +- src/delivery/graphql/generated.go | 100 ++++++++++++++++-- src/delivery/graphql/model/models_gen.go | 4 + .../resolver/qbittorrent_query.resolvers.go | 18 +++- src/delivery/graphql/resolver/utils.go | 15 +++ ui/lib/api/schema.graphql | 5 +- 6 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 src/delivery/graphql/resolver/utils.go diff --git a/graphql/sources/qbittorrent_query.graphql b/graphql/sources/qbittorrent_query.graphql index f71a8c8..af8dd2e 100644 --- a/graphql/sources/qbittorrent_query.graphql +++ b/graphql/sources/qbittorrent_query.graphql @@ -1,3 +1,7 @@ type QBitTorrentDaemonQuery { - torrents: [QTorrent!]! @resolver + torrents(filter: QBitTorrentDaemonFilter): [QTorrent!]! @resolver +} + +input QBitTorrentDaemonFilter { + sourcesCount: IntFilter } diff --git a/src/delivery/graphql/generated.go b/src/delivery/graphql/generated.go index de5e82e..762af3a 100644 --- a/src/delivery/graphql/generated.go +++ b/src/delivery/graphql/generated.go @@ -102,7 +102,7 @@ type ComplexityRoot struct { } QBitTorrentDaemonQuery struct { - Torrents func(childComplexity int) int + Torrents func(childComplexity int, filter *model.QBitTorrentDaemonFilter) int } QTorrent struct { @@ -243,7 +243,7 @@ type QBitTorrentDaemonMutationResolver interface { CleanupUnregistred(ctx context.Context, obj *model.QBitTorrentDaemonMutation, run bool) (*model.QBitCleanupUnregistredResponse, error) } type QBitTorrentDaemonQueryResolver interface { - Torrents(ctx context.Context, obj *model.QBitTorrentDaemonQuery) ([]*model.QTorrent, error) + Torrents(ctx context.Context, obj *model.QBitTorrentDaemonQuery, filter *model.QBitTorrentDaemonFilter) ([]*model.QTorrent, error) } type QTorrentResolver interface { SourceFiles(ctx context.Context, obj *model.QTorrent) ([]string, error) @@ -438,7 +438,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in break } - return e.complexity.QBitTorrentDaemonQuery.Torrents(childComplexity), true + args, err := ec.field_QBitTorrentDaemonQuery_torrents_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.QBitTorrentDaemonQuery.Torrents(childComplexity, args["filter"].(*model.QBitTorrentDaemonFilter)), true case "QTorrent.hash": if e.complexity.QTorrent.Hash == nil { @@ -970,6 +975,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputDateTimeFilter, ec.unmarshalInputIntFilter, ec.unmarshalInputPagination, + ec.unmarshalInputQBitTorrentDaemonFilter, ec.unmarshalInputStringFilter, ec.unmarshalInputTorrentFilter, ec.unmarshalInputTorrentPriorityFilter, @@ -1153,7 +1159,11 @@ type QBitCleanupUnregistredResponse { } `, BuiltIn: false}, {Name: "../../../graphql/sources/qbittorrent_query.graphql", Input: `type QBitTorrentDaemonQuery { - torrents: [QTorrent!]! @resolver + torrents(filter: QBitTorrentDaemonFilter): [QTorrent!]! @resolver +} + +input QBitTorrentDaemonFilter { + sourcesCount: IntFilter } `, BuiltIn: false}, {Name: "../../../graphql/sources/qbittorrent_types.graphql", Input: `type QTorrent { @@ -1484,6 +1494,38 @@ func (ec *executionContext) field_QBitTorrentDaemonMutation_cleanup_argsRun( return zeroVal, nil } +func (ec *executionContext) field_QBitTorrentDaemonQuery_torrents_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + arg0, err := ec.field_QBitTorrentDaemonQuery_torrents_argsFilter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["filter"] = arg0 + return args, nil +} +func (ec *executionContext) field_QBitTorrentDaemonQuery_torrents_argsFilter( + ctx context.Context, + rawArgs map[string]interface{}, +) (*model.QBitTorrentDaemonFilter, error) { + // We won't call the directive if the argument is null. + // Set call_argument_directives_with_null to true to call directives + // even if the argument is null. + _, ok := rawArgs["filter"] + if !ok { + var zeroVal *model.QBitTorrentDaemonFilter + return zeroVal, nil + } + + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter")) + if tmp, ok := rawArgs["filter"]; ok { + return ec.unmarshalOQBitTorrentDaemonFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQBitTorrentDaemonFilter(ctx, tmp) + } + + var zeroVal *model.QBitTorrentDaemonFilter + return zeroVal, nil +} + func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -2803,7 +2845,7 @@ func (ec *executionContext) _QBitTorrentDaemonQuery_torrents(ctx context.Context resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.QBitTorrentDaemonQuery().Torrents(rctx, obj) + return ec.resolvers.QBitTorrentDaemonQuery().Torrents(rctx, obj, fc.Args["filter"].(*model.QBitTorrentDaemonFilter)) } directive1 := func(ctx context.Context) (interface{}, error) { @@ -2841,7 +2883,7 @@ func (ec *executionContext) _QBitTorrentDaemonQuery_torrents(ctx context.Context return ec.marshalNQTorrent2ᚕᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQTorrentᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_QBitTorrentDaemonQuery_torrents(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_QBitTorrentDaemonQuery_torrents(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "QBitTorrentDaemonQuery", Field: field, @@ -2859,6 +2901,17 @@ func (ec *executionContext) fieldContext_QBitTorrentDaemonQuery_torrents(_ conte return nil, fmt.Errorf("no field named %q was found under type QTorrent", field.Name) }, } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_QBitTorrentDaemonQuery_torrents_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } return fc, nil } @@ -8820,6 +8873,33 @@ func (ec *executionContext) unmarshalInputPagination(ctx context.Context, obj in return it, nil } +func (ec *executionContext) unmarshalInputQBitTorrentDaemonFilter(ctx context.Context, obj interface{}) (model.QBitTorrentDaemonFilter, error) { + var it model.QBitTorrentDaemonFilter + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"sourcesCount"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "sourcesCount": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("sourcesCount")) + data, err := ec.unmarshalOIntFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐIntFilter(ctx, v) + if err != nil { + return it, err + } + it.SourcesCount = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputStringFilter(ctx context.Context, obj interface{}) (model.StringFilter, error) { var it model.StringFilter asMap := map[string]interface{}{} @@ -12561,6 +12641,14 @@ func (ec *executionContext) marshalOProgress2gitᚗkmsignᚗruᚋroyalcatᚋtsto return ec._Progress(ctx, sel, v) } +func (ec *executionContext) unmarshalOQBitTorrentDaemonFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQBitTorrentDaemonFilter(ctx context.Context, v interface{}) (*model.QBitTorrentDaemonFilter, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputQBitTorrentDaemonFilter(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) marshalOQBitTorrentDaemonMutation2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐQBitTorrentDaemonMutation(ctx context.Context, sel ast.SelectionSet, v *model.QBitTorrentDaemonMutation) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/src/delivery/graphql/model/models_gen.go b/src/delivery/graphql/model/models_gen.go index 00e2c04..afdfe49 100644 --- a/src/delivery/graphql/model/models_gen.go +++ b/src/delivery/graphql/model/models_gen.go @@ -106,6 +106,10 @@ type QBitCleanupUnregistredResponse struct { Hashes []string `json:"hashes"` } +type QBitTorrentDaemonFilter struct { + SourcesCount *IntFilter `json:"sourcesCount,omitempty"` +} + type QBitTorrentDaemonMutation struct { Cleanup *QBitCleanupResponse `json:"cleanup"` CleanupUnregistred *QBitCleanupUnregistredResponse `json:"cleanupUnregistred"` diff --git a/src/delivery/graphql/resolver/qbittorrent_query.resolvers.go b/src/delivery/graphql/resolver/qbittorrent_query.resolvers.go index 11b0e45..a86a783 100644 --- a/src/delivery/graphql/resolver/qbittorrent_query.resolvers.go +++ b/src/delivery/graphql/resolver/qbittorrent_query.resolvers.go @@ -7,13 +7,14 @@ package resolver import ( "context" "fmt" + "slices" graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql" "git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model" ) // Torrents is the resolver for the torrents field. -func (r *qBitTorrentDaemonQueryResolver) Torrents(ctx context.Context, obj *model.QBitTorrentDaemonQuery) ([]*model.QTorrent, error) { +func (r *qBitTorrentDaemonQueryResolver) Torrents(ctx context.Context, obj *model.QBitTorrentDaemonQuery, filter *model.QBitTorrentDaemonFilter) ([]*model.QTorrent, error) { info, err := r.QBitTorrentDaemon.ListTorrents(ctx) if err != nil { return nil, fmt.Errorf("error listing torrents: %w", err) @@ -27,6 +28,21 @@ func (r *qBitTorrentDaemonQueryResolver) Torrents(ctx context.Context, obj *mode } } + if filter != nil { + if filter.SourcesCount != nil { + for _, t := range out { + srcs, err := r.QBitTorrentDaemon.SourceFiles(ctx, t.Hash) + if err != nil { + return nil, fmt.Errorf("hash %s failed listing source files: %w", t.Hash, err) + } + t.SourceFiles = srcs + } + + out = slices.DeleteFunc(out, func(t *model.QTorrent) bool { + return !filter.SourcesCount.Include(int64(len(t.SourceFiles))) + }) + } + } return out, nil } diff --git a/src/delivery/graphql/resolver/utils.go b/src/delivery/graphql/resolver/utils.go new file mode 100644 index 0000000..3d62d17 --- /dev/null +++ b/src/delivery/graphql/resolver/utils.go @@ -0,0 +1,15 @@ +package resolver + +import "iter" + +func Filter1Func[K, V any](S iter.Seq2[K, V], match func(V) bool) iter.Seq2[K, V] { + return func(yield func(K, V) bool) { + for k, v := range S { + if match(v) { + if !yield(k, v) { + break + } + } + } + } +} diff --git a/ui/lib/api/schema.graphql b/ui/lib/api/schema.graphql index f1673c9..3154319 100644 --- a/ui/lib/api/schema.graphql +++ b/ui/lib/api/schema.graphql @@ -65,12 +65,15 @@ type QBitCleanupUnregistredResponse { count: Int! hashes: [String!]! } +input QBitTorrentDaemonFilter { + multipleSources: Boolean +} type QBitTorrentDaemonMutation { cleanup(run: Boolean!): QBitCleanupResponse! @resolver cleanupUnregistred(run: Boolean!): QBitCleanupUnregistredResponse! @resolver } type QBitTorrentDaemonQuery { - torrents: [QTorrent!]! @resolver + torrents(filter: QBitTorrentDaemonFilter): [QTorrent!]! @resolver } type QTorrent { name: String!