Compare commits
2 commits
0fa3a91447
...
199a82ff0c
Author | SHA1 | Date | |
---|---|---|---|
199a82ff0c | |||
13ce2aa07f |
34 changed files with 2231 additions and 961 deletions
42
.gqlgen.yml
42
.gqlgen.yml
|
@ -22,18 +22,9 @@ models:
|
||||||
Int:
|
Int:
|
||||||
model: github.com/99designs/gqlgen/graphql.Int64
|
model: github.com/99designs/gqlgen/graphql.Int64
|
||||||
Torrent:
|
Torrent:
|
||||||
fields:
|
|
||||||
name:
|
|
||||||
resolver: true
|
|
||||||
files:
|
|
||||||
resolver: true
|
|
||||||
excludedFiles:
|
|
||||||
resolver: true
|
|
||||||
peers:
|
|
||||||
resolver: true
|
|
||||||
extraFields:
|
extraFields:
|
||||||
T:
|
T:
|
||||||
type: "*git.kmsign.ru/royalcat/tstor/src/host/torrent.Controller"
|
type: "*git.kmsign.ru/royalcat/tstor/src/sources/torrent.Controller"
|
||||||
TorrentFile:
|
TorrentFile:
|
||||||
extraFields:
|
extraFields:
|
||||||
F:
|
F:
|
||||||
|
@ -43,32 +34,37 @@ models:
|
||||||
F:
|
F:
|
||||||
type: "*github.com/anacrolix/torrent.PeerConn"
|
type: "*github.com/anacrolix/torrent.PeerConn"
|
||||||
SimpleDir:
|
SimpleDir:
|
||||||
fields:
|
|
||||||
entries:
|
|
||||||
resolver: true
|
|
||||||
extraFields:
|
extraFields:
|
||||||
Path:
|
Path:
|
||||||
type: string
|
type: string
|
||||||
FS:
|
FS:
|
||||||
type: "git.kmsign.ru/royalcat/tstor/src/vfs.Filesystem"
|
type: "git.kmsign.ru/royalcat/tstor/src/vfs.Filesystem"
|
||||||
TorrentFS:
|
TorrentFS:
|
||||||
fields:
|
|
||||||
entries:
|
|
||||||
resolver: true
|
|
||||||
extraFields:
|
extraFields:
|
||||||
FS:
|
FS:
|
||||||
type: "*git.kmsign.ru/royalcat/tstor/src/host/torrent.TorrentFS"
|
type: "*git.kmsign.ru/royalcat/tstor/src/sources/torrent.TorrentFS"
|
||||||
ResolverFS:
|
ResolverFS:
|
||||||
fields:
|
|
||||||
entries:
|
|
||||||
resolver: true
|
|
||||||
extraFields:
|
extraFields:
|
||||||
FS:
|
FS:
|
||||||
type: "*git.kmsign.ru/royalcat/tstor/src/vfs.ResolverFS"
|
type: "*git.kmsign.ru/royalcat/tstor/src/vfs.ResolverFS"
|
||||||
ArchiveFS:
|
ArchiveFS:
|
||||||
fields:
|
|
||||||
entries:
|
|
||||||
resolver: true
|
|
||||||
extraFields:
|
extraFields:
|
||||||
FS:
|
FS:
|
||||||
type: "*git.kmsign.ru/royalcat/tstor/src/vfs.ArchiveFS"
|
type: "*git.kmsign.ru/royalcat/tstor/src/vfs.ArchiveFS"
|
||||||
|
TorrentOps:
|
||||||
|
extraFields:
|
||||||
|
InfoHash:
|
||||||
|
type: "string"
|
||||||
|
TorrentPriority:
|
||||||
|
model: "github.com/anacrolix/torrent/types.PiecePriority"
|
||||||
|
enum_values:
|
||||||
|
NONE:
|
||||||
|
value: "github.com/anacrolix/torrent/types.PiecePriorityNone"
|
||||||
|
NORMAL:
|
||||||
|
value: "github.com/anacrolix/torrent/types.PiecePriorityNormal"
|
||||||
|
HIGH:
|
||||||
|
value: "github.com/anacrolix/torrent/types.PiecePriorityHigh"
|
||||||
|
READAHEAD:
|
||||||
|
value: "github.com/anacrolix/torrent/types.PiecePriorityReadahead"
|
||||||
|
NOW:
|
||||||
|
value: "github.com/anacrolix/torrent/types.PiecePriorityNow"
|
||||||
|
|
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
|
@ -5,14 +5,14 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Launch file",
|
"name": "Generate GraphQL",
|
||||||
"type": "go",
|
"type": "go",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mode": "debug",
|
"mode": "debug",
|
||||||
"program": "${file}"
|
"program": "${workspaceFolder}/cmd/generate-graphql/main.go",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Launch Package",
|
"name": "TStor",
|
||||||
"type": "go",
|
"type": "go",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"mode": "auto",
|
"mode": "auto",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.22 as builder
|
FROM --platform=$BUILDPLATFORM golang:1.22 as builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@ -14,7 +14,9 @@ COPY ./templates ./templates
|
||||||
COPY embed.go embed.go
|
COPY embed.go embed.go
|
||||||
|
|
||||||
RUN go generate ./...
|
RUN go generate ./...
|
||||||
RUN --mount=type=cache,mode=0777,target=/go/pkg/mod CGO_ENABLED=0 go build -tags timetzdata -o /tstor ./cmd/tstor/main.go
|
|
||||||
|
ARG TARGETOS TARGETARCH
|
||||||
|
RUN --mount=type=cache,mode=0777,target=/go/pkg/mod CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -tags timetzdata -o /tstor ./cmd/tstor/main.go
|
||||||
|
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -1,6 +1,6 @@
|
||||||
generate-graphql: src/delivery/graphql/generated.go ui/lib/api/schema.graphql
|
generate-graphql: src/delivery/graphql/generated.go ui/lib/api/schema.graphql
|
||||||
|
|
||||||
src/delivery/graphql/generated.go: .gqlgen.yml graphql/* graphql/types/* cmd/generate-graphql/*
|
src/delivery/graphql/generated.go: .gqlgen.yml cmd/generate-graphql/* $(shell find graphql -type f)
|
||||||
go run cmd/generate-graphql/main.go
|
go run cmd/generate-graphql/main.go
|
||||||
|
|
||||||
ui/lib/api/schema.graphql: src/delivery/graphql/* cmd/generate-graphql-schema/*
|
ui/lib/api/schema.graphql: src/delivery/graphql/* cmd/generate-graphql-schema/*
|
||||||
|
|
|
@ -10,6 +10,23 @@ import (
|
||||||
"github.com/99designs/gqlgen/codegen/config"
|
"github.com/99designs/gqlgen/codegen/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cfg, err := config.LoadConfigFromDefaultLocations()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.Generate(cfg,
|
||||||
|
api.PrependPlugin(&resolverDirective{}),
|
||||||
|
api.AddPlugin(&fieldDirectiveFix{}),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
os.Exit(3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type fieldDirectiveFix struct {
|
type fieldDirectiveFix struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,9 +42,6 @@ func (fieldDirectiveFix) GenerateCode(cfg *codegen.Data) error {
|
||||||
for _, v := range field.TypeReference.Definition.Directives {
|
for _, v := range field.TypeReference.Definition.Directives {
|
||||||
directiveMap[v.Name]++
|
directiveMap[v.Name]++
|
||||||
}
|
}
|
||||||
// for _, v := range field.Object.Directives {
|
|
||||||
// directiveMap[v.Name]++
|
|
||||||
// }
|
|
||||||
|
|
||||||
directive := make([]*codegen.Directive, 0, len(field.Directives))
|
directive := make([]*codegen.Directive, 0, len(field.Directives))
|
||||||
for _, v := range field.Directives {
|
for _, v := range field.Directives {
|
||||||
|
@ -48,18 +62,33 @@ func (fieldDirectiveFix) GenerateCode(cfg *codegen.Data) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
type resolverDirective struct {
|
||||||
cfg, err := config.LoadConfigFromDefaultLocations()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = api.Generate(cfg,
|
func (resolverDirective) Name() string {
|
||||||
api.AddPlugin(&fieldDirectiveFix{}),
|
return "Resolver directive support"
|
||||||
)
|
}
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err.Error())
|
func (resolverDirective) GenerateCode(cfg *codegen.Data) error {
|
||||||
os.Exit(3)
|
const directiveName = "resolver"
|
||||||
|
|
||||||
|
for _, obj := range cfg.Objects {
|
||||||
|
for _, field := range obj.Fields {
|
||||||
|
if field.FieldDefinition.Directives.ForName(directiveName) != nil {
|
||||||
|
fmt.Printf("Add resolver for field %s{%s}\n", obj.Name, field.Name)
|
||||||
|
field.IsResolver = true
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// field.FieldDefinition.Directives = removeDirective(field.FieldDefinition.Directives, directiveName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func removeDirective(directives ast.DirectiveList, name string) ast.DirectiveList {
|
||||||
|
// return slices.DeleteFunc(directives, func(directive *ast.Directive) bool {
|
||||||
|
// return directive.Name == name
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
20
go.mod
20
go.mod
|
@ -3,13 +3,13 @@ module git.kmsign.ru/royalcat/tstor
|
||||||
go 1.22.3
|
go 1.22.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/gqlgen v0.17.45
|
github.com/99designs/gqlgen v0.17.49
|
||||||
github.com/agoda-com/opentelemetry-go/otelslog v0.1.1
|
github.com/agoda-com/opentelemetry-go/otelslog v0.1.1
|
||||||
github.com/agoda-com/opentelemetry-logs-go v0.5.0
|
github.com/agoda-com/opentelemetry-logs-go v0.5.0
|
||||||
github.com/anacrolix/dht/v2 v2.21.1
|
github.com/anacrolix/dht/v2 v2.21.1
|
||||||
github.com/anacrolix/log v0.15.2
|
github.com/anacrolix/log v0.15.2
|
||||||
github.com/anacrolix/missinggo/v2 v2.7.3
|
github.com/anacrolix/missinggo/v2 v2.7.3
|
||||||
github.com/anacrolix/torrent v1.56.0
|
github.com/anacrolix/torrent v1.56.1
|
||||||
github.com/billziss-gh/cgofuse v1.5.0
|
github.com/billziss-gh/cgofuse v1.5.0
|
||||||
github.com/bodgit/sevenzip v1.5.1
|
github.com/bodgit/sevenzip v1.5.1
|
||||||
github.com/cyphar/filepath-securejoin v0.2.5
|
github.com/cyphar/filepath-securejoin v0.2.5
|
||||||
|
@ -43,7 +43,7 @@ require (
|
||||||
github.com/samber/slog-zerolog v1.0.0
|
github.com/samber/slog-zerolog v1.0.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/urfave/cli/v2 v2.27.2
|
github.com/urfave/cli/v2 v2.27.2
|
||||||
github.com/vektah/gqlparser/v2 v2.5.11
|
github.com/vektah/gqlparser/v2 v2.5.16
|
||||||
github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00
|
github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00
|
||||||
github.com/willscott/memphis v0.0.0-20210922141505-529d4987ab7e
|
github.com/willscott/memphis v0.0.0-20210922141505-529d4987ab7e
|
||||||
go.opentelemetry.io/otel v1.27.0
|
go.opentelemetry.io/otel v1.27.0
|
||||||
|
@ -54,9 +54,9 @@ require (
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.27.0
|
go.opentelemetry.io/otel/sdk/metric v1.27.0
|
||||||
go.opentelemetry.io/otel/trace v1.27.0
|
go.opentelemetry.io/otel/trace v1.27.0
|
||||||
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||||
golang.org/x/net v0.25.0
|
golang.org/x/net v0.26.0
|
||||||
golang.org/x/sync v0.7.0
|
golang.org/x/sync v0.7.0
|
||||||
golang.org/x/sys v0.20.0
|
golang.org/x/sys v0.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -168,7 +168,7 @@ require (
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
|
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
|
||||||
github.com/samber/lo v1.39.0 // indirect
|
github.com/samber/lo v1.39.0 // indirect
|
||||||
github.com/sosodev/duration v1.3.0 // indirect
|
github.com/sosodev/duration v1.3.1 // indirect
|
||||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||||
github.com/tidwall/btree v1.7.0 // indirect
|
github.com/tidwall/btree v1.7.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
@ -185,11 +185,11 @@ require (
|
||||||
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
||||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
golang.org/x/crypto v0.23.0 // indirect
|
golang.org/x/crypto v0.24.0 // indirect
|
||||||
golang.org/x/mod v0.17.0 // indirect
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.21.0 // indirect
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
|
||||||
google.golang.org/grpc v1.64.0 // indirect
|
google.golang.org/grpc v1.64.0 // indirect
|
||||||
|
|
40
go.sum
40
go.sum
|
@ -19,8 +19,8 @@ crawshaw.io/sqlite v0.3.2/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||||
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
|
||||||
github.com/99designs/gqlgen v0.17.45 h1:bH0AH67vIJo8JKNKPJP+pOPpQhZeuVRQLf53dKIpDik=
|
github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ=
|
||||||
github.com/99designs/gqlgen v0.17.45/go.mod h1:Bas0XQ+Jiu/Xm5E33jC8sES3G+iC2esHBMXcq0fUPs0=
|
github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||||
|
@ -99,8 +99,8 @@ github.com/anacrolix/sync v0.5.1/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DC
|
||||||
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||||
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
|
||||||
github.com/anacrolix/torrent v1.56.0 h1:g/sM0K/BaWUv4Htu2bblLBhIxGdFZ1MUCoD7lcvemlo=
|
github.com/anacrolix/torrent v1.56.1 h1:QeJMOP0NuhpQ5dATsOqEL0vUO85aPMNMGP2FACNt0Eg=
|
||||||
github.com/anacrolix/torrent v1.56.0/go.mod h1:5DMHbeIM1TuC5wTQ99XieKKLiYZYz6iB2lyZpKZEr6w=
|
github.com/anacrolix/torrent v1.56.1/go.mod h1:5DMHbeIM1TuC5wTQ99XieKKLiYZYz6iB2lyZpKZEr6w=
|
||||||
github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U=
|
github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U=
|
||||||
github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
|
github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
|
||||||
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
|
||||||
|
@ -582,8 +582,8 @@ github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:K
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||||
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM=
|
||||||
github.com/sosodev/duration v1.3.0 h1:g3E6mto+hFdA2uZXeNDYff8LYeg7v5D4YKP/Ng/NUkE=
|
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||||
github.com/sosodev/duration v1.3.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
@ -622,8 +622,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
github.com/vektah/gqlparser/v2 v2.5.11 h1:JJxLtXIoN7+3x6MBdtIP59TP1RANnY7pXOaDnADQSf8=
|
github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8=
|
||||||
github.com/vektah/gqlparser/v2 v2.5.11/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc=
|
github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww=
|
||||||
github.com/warpfork/go-errcat v0.0.0-20180917083543-335044ffc86e h1:FIB2fi7XJGHIdf5rWNsfFQqatIKxutT45G+wNuMQNgs=
|
github.com/warpfork/go-errcat v0.0.0-20180917083543-335044ffc86e h1:FIB2fi7XJGHIdf5rWNsfFQqatIKxutT45G+wNuMQNgs=
|
||||||
github.com/warpfork/go-errcat v0.0.0-20180917083543-335044ffc86e/go.mod h1:/qe02xr3jvTUz8u/PV0FHGpP8t96OQNP7U9BJMwMLEw=
|
github.com/warpfork/go-errcat v0.0.0-20180917083543-335044ffc86e/go.mod h1:/qe02xr3jvTUz8u/PV0FHGpP8t96OQNP7U9BJMwMLEw=
|
||||||
github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
|
github.com/warpfork/go-wish v0.0.0-20200122115046-b9ea61034e4a/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
|
||||||
|
@ -695,8 +695,8 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
@ -730,8 +730,8 @@ golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -764,8 +764,8 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -826,8 +826,8 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
@ -850,8 +850,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
@ -887,8 +887,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||||
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8-0.20211029000441-d6a9af8af023/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -1,26 +1,10 @@
|
||||||
type Mutation {
|
type Mutation {
|
||||||
validateTorrents(filter: TorrentFilter!): Boolean!
|
torrentDaemon: TorrentDaemonMutation @resolver
|
||||||
cleanupTorrents(files: Boolean, dryRun: Boolean!): CleanupResponse!
|
|
||||||
downloadTorrent(infohash: String!, file: String): DownloadTorrentResponse
|
|
||||||
uploadFile(dir: String!, file: Upload!): Boolean!
|
uploadFile(dir: String!, file: Upload!): Boolean!
|
||||||
dedupeStorage: Int!
|
dedupeStorage: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
input TorrentFilter @oneOf {
|
|
||||||
everything: Boolean
|
|
||||||
infohash: String
|
|
||||||
# pathGlob: String!
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownloadTorrentResponse {
|
|
||||||
task: Task
|
|
||||||
}
|
|
||||||
|
|
||||||
type CleanupResponse {
|
|
||||||
count: Int!
|
|
||||||
list: [String!]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type Task {
|
type Task {
|
||||||
id: ID!
|
id: ID!
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,5 @@
|
||||||
type Query {
|
type Query {
|
||||||
torrents(filter: TorrentsFilter): [Torrent!]!
|
torrentDaemon: TorrentDaemonQuery @resolver
|
||||||
|
|
||||||
fsEntry(path: String!): FsEntry
|
fsEntry(path: String!): FsEntry
|
||||||
}
|
}
|
||||||
|
|
||||||
input TorrentsFilter {
|
|
||||||
infohash: StringFilter
|
|
||||||
name: StringFilter
|
|
||||||
bytesCompleted: IntFilter
|
|
||||||
bytesMissing: IntFilter
|
|
||||||
|
|
||||||
peersCount: IntFilter
|
|
||||||
downloading: BooleanFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
input Pagination {
|
|
||||||
offset: Int!
|
|
||||||
limit: Int!
|
|
||||||
}
|
|
||||||
|
|
||||||
input StringFilter @oneOf {
|
|
||||||
eq: String
|
|
||||||
substr: String
|
|
||||||
in: [String!]
|
|
||||||
}
|
|
||||||
|
|
||||||
input IntFilter @oneOf {
|
|
||||||
eq: Int
|
|
||||||
gt: Int
|
|
||||||
lt: Int
|
|
||||||
gte: Int
|
|
||||||
lte: Int
|
|
||||||
in: [Int!]
|
|
||||||
}
|
|
||||||
|
|
||||||
input DateTimeFilter @oneOf {
|
|
||||||
eq: DateTime
|
|
||||||
gt: DateTime
|
|
||||||
lt: DateTime
|
|
||||||
gte: DateTime
|
|
||||||
lte: DateTime
|
|
||||||
}
|
|
||||||
|
|
||||||
input BooleanFilter @oneOf {
|
|
||||||
eq: Boolean
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
|
directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
|
||||||
|
directive @resolver on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
||||||
|
|
||||||
directive @stream on FIELD_DEFINITION
|
directive @stream on FIELD_DEFINITION
|
||||||
|
|
||||||
scalar DateTime
|
scalar DateTime
|
||||||
|
|
18
graphql/sources/torrent_mutation.graphql
Normal file
18
graphql/sources/torrent_mutation.graphql
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
type TorrentDaemonMutation {
|
||||||
|
validateTorrent(filter: TorrentFilter!): Boolean! @resolver
|
||||||
|
setTorrentPriority(
|
||||||
|
infohash: String!
|
||||||
|
file: String
|
||||||
|
priority: TorrentPriority!
|
||||||
|
): Boolean! @resolver
|
||||||
|
cleanup(files: Boolean, dryRun: Boolean!): CleanupResponse! @resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
type CleanupResponse {
|
||||||
|
count: Int!
|
||||||
|
list: [String!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownloadTorrentResponse {
|
||||||
|
task: Task
|
||||||
|
}
|
27
graphql/sources/torrent_query.graphql
Normal file
27
graphql/sources/torrent_query.graphql
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
type TorrentDaemonQuery {
|
||||||
|
torrents(filter: TorrentsFilter): [Torrent!]! @resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
input TorrentsFilter {
|
||||||
|
infohash: StringFilter
|
||||||
|
name: StringFilter
|
||||||
|
bytesCompleted: IntFilter
|
||||||
|
bytesMissing: IntFilter
|
||||||
|
peersCount: IntFilter
|
||||||
|
priority: TorrentPriorityFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
input TorrentPriorityFilter @oneOf {
|
||||||
|
eq: TorrentPriority
|
||||||
|
gt: TorrentPriority
|
||||||
|
lt: TorrentPriority
|
||||||
|
gte: TorrentPriority
|
||||||
|
lte: TorrentPriority
|
||||||
|
in: [TorrentPriority!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input TorrentFilter @oneOf {
|
||||||
|
everything: Boolean
|
||||||
|
infohash: String
|
||||||
|
# pathGlob: String!
|
||||||
|
}
|
|
@ -1,15 +1,13 @@
|
||||||
type Torrent {
|
type Torrent {
|
||||||
name: String!
|
name: String! @resolver
|
||||||
infohash: String!
|
infohash: String!
|
||||||
bytesCompleted: Int!
|
bytesCompleted: Int!
|
||||||
torrentFilePath: String!
|
torrentFilePath: String!
|
||||||
bytesMissing: Int!
|
bytesMissing: Int!
|
||||||
files: [TorrentFile!]!
|
priority: TorrentPriority! @resolver
|
||||||
excludedFiles: [TorrentFile!]!
|
files: [TorrentFile!]! @resolver
|
||||||
peers: [TorrentPeer!]!
|
excludedFiles: [TorrentFile!]! @resolver
|
||||||
|
peers: [TorrentPeer!]! @resolver
|
||||||
# if at least one piece of the torrent is request to download and not already downloaded
|
|
||||||
downloading: Boolean!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TorrentFile {
|
type TorrentFile {
|
||||||
|
@ -25,3 +23,11 @@ type TorrentPeer {
|
||||||
port: Int!
|
port: Int!
|
||||||
clientName: String!
|
clientName: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TorrentPriority {
|
||||||
|
NONE
|
||||||
|
NORMAL
|
||||||
|
HIGH
|
||||||
|
READAHEAD
|
||||||
|
NOW
|
||||||
|
}
|
31
graphql/types/filters.graphql
Normal file
31
graphql/types/filters.graphql
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
input Pagination {
|
||||||
|
offset: Int!
|
||||||
|
limit: Int!
|
||||||
|
}
|
||||||
|
|
||||||
|
input StringFilter @oneOf {
|
||||||
|
eq: String
|
||||||
|
substr: String
|
||||||
|
in: [String!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input IntFilter @oneOf {
|
||||||
|
eq: Int
|
||||||
|
gt: Int
|
||||||
|
lt: Int
|
||||||
|
gte: Int
|
||||||
|
lte: Int
|
||||||
|
in: [Int!]
|
||||||
|
}
|
||||||
|
|
||||||
|
input DateTimeFilter @oneOf {
|
||||||
|
eq: DateTime
|
||||||
|
gt: DateTime
|
||||||
|
lt: DateTime
|
||||||
|
gte: DateTime
|
||||||
|
lte: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
input BooleanFilter @oneOf {
|
||||||
|
eq: Boolean
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ interface File implements FsEntry {
|
||||||
|
|
||||||
type SimpleDir implements Dir & FsEntry {
|
type SimpleDir implements Dir & FsEntry {
|
||||||
name: String!
|
name: String!
|
||||||
entries: [FsEntry!]!
|
entries: [FsEntry!]! @resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
type SimpleFile implements File & FsEntry {
|
type SimpleFile implements File & FsEntry {
|
||||||
|
@ -24,12 +24,12 @@ type SimpleFile implements File & FsEntry {
|
||||||
|
|
||||||
type ResolverFS implements Dir & FsEntry {
|
type ResolverFS implements Dir & FsEntry {
|
||||||
name: String!
|
name: String!
|
||||||
entries: [FsEntry!]!
|
entries: [FsEntry!]! @resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArchiveFS implements Dir & FsEntry {
|
type ArchiveFS implements Dir & FsEntry {
|
||||||
name: String!
|
name: String!
|
||||||
entries: [FsEntry!]!
|
entries: [FsEntry!]! @resolver
|
||||||
|
|
||||||
size: Int!
|
size: Int!
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ type ArchiveFS implements Dir & FsEntry {
|
||||||
type TorrentFS implements Dir & FsEntry {
|
type TorrentFS implements Dir & FsEntry {
|
||||||
name: String!
|
name: String!
|
||||||
torrent: Torrent!
|
torrent: Torrent!
|
||||||
entries: [FsEntry!]!
|
entries: [FsEntry!]! @resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
type TorrentFileEntry implements File & FsEntry {
|
type TorrentFileEntry implements File & FsEntry {
|
||||||
|
|
|
@ -35,23 +35,34 @@ func AddHandler(nh slog.Handler) {
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
handler slog.Handler
|
handler slog.Handler
|
||||||
|
callNesting int
|
||||||
component []string
|
component []string
|
||||||
}
|
}
|
||||||
|
|
||||||
const functionKey = "function"
|
const functionKey = "function"
|
||||||
|
|
||||||
|
const componentKey = "component"
|
||||||
|
const componentSep = "."
|
||||||
|
|
||||||
func (l *Logger) log(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
|
func (l *Logger) log(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
|
||||||
var pcs [1]uintptr
|
var pcs [1]uintptr
|
||||||
runtime.Callers(3, pcs[:])
|
runtime.Callers(3+l.callNesting, pcs[:])
|
||||||
pc := pcs[0]
|
pc := pcs[0]
|
||||||
f := runtime.FuncForPC(pc)
|
f := runtime.FuncForPC(pc)
|
||||||
|
if f != nil {
|
||||||
attrs = append(attrs, slog.String(functionKey, f.Name()))
|
attrs = append(attrs, slog.String(functionKey, f.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(l.component) > 0 {
|
||||||
|
attrs = append(attrs, slog.String(componentKey, strings.Join(l.component, componentSep)))
|
||||||
|
}
|
||||||
|
|
||||||
r := slog.NewRecord(time.Now(), level, msg, pc)
|
r := slog.NewRecord(time.Now(), level, msg, pc)
|
||||||
r.AddAttrs(attrs...)
|
r.AddAttrs(attrs...)
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = l.handler.Handle(ctx, r)
|
_ = l.handler.Handle(ctx, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,16 +82,10 @@ func (l *Logger) Error(ctx context.Context, msg string, attrs ...slog.Attr) {
|
||||||
l.log(ctx, slog.LevelError, msg, attrs...)
|
l.log(ctx, slog.LevelError, msg, attrs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
const componentKey = "component"
|
|
||||||
const componentSep = "."
|
|
||||||
|
|
||||||
func (log *Logger) WithComponent(name string) *Logger {
|
func (log *Logger) WithComponent(name string) *Logger {
|
||||||
c := append(log.component, name)
|
|
||||||
return &Logger{
|
return &Logger{
|
||||||
handler: log.handler.WithAttrs([]slog.Attr{
|
handler: log.handler,
|
||||||
slog.String(componentKey, strings.Join(c, componentSep)),
|
component: append(log.component, name),
|
||||||
}),
|
|
||||||
component: c,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,23 +96,20 @@ func (l *Logger) With(attrs ...slog.Attr) *Logger {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Nested(callNesting int) *Logger {
|
||||||
|
return &Logger{
|
||||||
|
handler: l.handler,
|
||||||
|
component: l.component,
|
||||||
|
callNesting: callNesting,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// returns a new slog logger with the same attribures as the original logger
|
// returns a new slog logger with the same attribures as the original logger
|
||||||
// TODO currently not logging function name
|
// TODO currently not logging function name
|
||||||
func (l *Logger) Slog() *slog.Logger {
|
func (l *Logger) Slog() *slog.Logger {
|
||||||
return slog.New(l.handler)
|
return slog.New(l.handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
const endpointKey = "endpoint"
|
|
||||||
|
|
||||||
func (l *Logger) WithEndpoint(name string) *Logger {
|
|
||||||
return &Logger{
|
|
||||||
handler: l.handler.WithAttrs([]slog.Attr{
|
|
||||||
slog.String(endpointKey, name),
|
|
||||||
}),
|
|
||||||
component: l.component,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const errKey = "error"
|
const errKey = "error"
|
||||||
|
|
||||||
func Error(err error) slog.Attr {
|
func Error(err error) slog.Attr {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,6 +3,8 @@ package model
|
||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anacrolix/torrent/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Filter[T any] interface {
|
type Filter[T any] interface {
|
||||||
|
@ -52,3 +54,23 @@ func (f *BooleanFilter) Include(v bool) bool {
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *TorrentPriorityFilter) Include(v types.PiecePriority) bool {
|
||||||
|
if f == nil {
|
||||||
|
return true
|
||||||
|
} else if f.Eq != nil {
|
||||||
|
return v == *f.Eq
|
||||||
|
} else if f.Gt != nil {
|
||||||
|
return v > *f.Gt
|
||||||
|
} else if f.Gte != nil {
|
||||||
|
return v >= *f.Gte
|
||||||
|
} else if f.Lt != nil {
|
||||||
|
return v < *f.Lt
|
||||||
|
} else if f.Lte != nil {
|
||||||
|
return v <= *f.Lte
|
||||||
|
} else if f.In != nil {
|
||||||
|
return slices.Contains(f.In, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
|
@ -29,24 +29,11 @@ func MapPeerSource(source atorrent.PeerSource) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func MapTorrent(ctx context.Context, t *torrent.Controller) (*Torrent, error) {
|
func MapTorrent(ctx context.Context, t *torrent.Controller) (*Torrent, error) {
|
||||||
downloading := false
|
|
||||||
files, err := t.Files(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Priority() > atorrent.PiecePriorityNone && file.BytesCompleted() < file.Length() {
|
|
||||||
downloading = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Torrent{
|
return &Torrent{
|
||||||
Infohash: t.InfoHash(),
|
Infohash: t.InfoHash(),
|
||||||
Name: t.Name(),
|
Name: t.Name(),
|
||||||
BytesCompleted: t.BytesCompleted(),
|
BytesCompleted: t.BytesCompleted(),
|
||||||
BytesMissing: t.BytesMissing(),
|
BytesMissing: t.BytesMissing(),
|
||||||
T: t,
|
T: t,
|
||||||
Downloading: downloading,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"git.kmsign.ru/royalcat/tstor/src/sources/torrent"
|
"git.kmsign.ru/royalcat/tstor/src/sources/torrent"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
||||||
torrent1 "github.com/anacrolix/torrent"
|
torrent1 "github.com/anacrolix/torrent"
|
||||||
|
"github.com/anacrolix/torrent/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dir interface {
|
type Dir interface {
|
||||||
|
@ -176,13 +177,23 @@ type Torrent struct {
|
||||||
BytesCompleted int64 `json:"bytesCompleted"`
|
BytesCompleted int64 `json:"bytesCompleted"`
|
||||||
TorrentFilePath string `json:"torrentFilePath"`
|
TorrentFilePath string `json:"torrentFilePath"`
|
||||||
BytesMissing int64 `json:"bytesMissing"`
|
BytesMissing int64 `json:"bytesMissing"`
|
||||||
|
Priority types.PiecePriority `json:"priority"`
|
||||||
Files []*TorrentFile `json:"files"`
|
Files []*TorrentFile `json:"files"`
|
||||||
ExcludedFiles []*TorrentFile `json:"excludedFiles"`
|
ExcludedFiles []*TorrentFile `json:"excludedFiles"`
|
||||||
Peers []*TorrentPeer `json:"peers"`
|
Peers []*TorrentPeer `json:"peers"`
|
||||||
Downloading bool `json:"downloading"`
|
|
||||||
T *torrent.Controller `json:"-"`
|
T *torrent.Controller `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TorrentDaemonMutation struct {
|
||||||
|
ValidateTorrent bool `json:"validateTorrent"`
|
||||||
|
SetTorrentPriority bool `json:"setTorrentPriority"`
|
||||||
|
Cleanup *CleanupResponse `json:"cleanup"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TorrentDaemonQuery struct {
|
||||||
|
Torrents []*Torrent `json:"torrents"`
|
||||||
|
}
|
||||||
|
|
||||||
type TorrentFs struct {
|
type TorrentFs struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Torrent *Torrent `json:"torrent"`
|
Torrent *Torrent `json:"torrent"`
|
||||||
|
@ -238,6 +249,15 @@ type TorrentPeer struct {
|
||||||
F *torrent1.PeerConn `json:"-"`
|
F *torrent1.PeerConn `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TorrentPriorityFilter struct {
|
||||||
|
Eq *types.PiecePriority `json:"eq,omitempty"`
|
||||||
|
Gt *types.PiecePriority `json:"gt,omitempty"`
|
||||||
|
Lt *types.PiecePriority `json:"lt,omitempty"`
|
||||||
|
Gte *types.PiecePriority `json:"gte,omitempty"`
|
||||||
|
Lte *types.PiecePriority `json:"lte,omitempty"`
|
||||||
|
In []types.PiecePriority `json:"in,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type TorrentProgress struct {
|
type TorrentProgress struct {
|
||||||
Torrent *Torrent `json:"torrent"`
|
Torrent *Torrent `json:"torrent"`
|
||||||
Current int64 `json:"current"`
|
Current int64 `json:"current"`
|
||||||
|
@ -254,5 +274,5 @@ type TorrentsFilter struct {
|
||||||
BytesCompleted *IntFilter `json:"bytesCompleted,omitempty"`
|
BytesCompleted *IntFilter `json:"bytesCompleted,omitempty"`
|
||||||
BytesMissing *IntFilter `json:"bytesMissing,omitempty"`
|
BytesMissing *IntFilter `json:"bytesMissing,omitempty"`
|
||||||
PeersCount *IntFilter `json:"peersCount,omitempty"`
|
PeersCount *IntFilter `json:"peersCount,omitempty"`
|
||||||
Downloading *BooleanFilter `json:"downloading,omitempty"`
|
Priority *TorrentPriorityFilter `json:"priority,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package resolver
|
||||||
|
|
||||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
// will be copied through when generating and any unknown code will be moved to the end.
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
// Code generated by github.com/99designs/gqlgen version v0.17.49
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
|
@ -2,7 +2,7 @@ package resolver
|
||||||
|
|
||||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
// will be copied through when generating and any unknown code will be moved to the end.
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
// Code generated by github.com/99designs/gqlgen version v0.17.49
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -11,84 +11,14 @@ import (
|
||||||
"os"
|
"os"
|
||||||
pathlib "path"
|
pathlib "path"
|
||||||
|
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/uuid"
|
|
||||||
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/sources/torrent"
|
|
||||||
"github.com/99designs/gqlgen/graphql"
|
"github.com/99designs/gqlgen/graphql"
|
||||||
aih "github.com/anacrolix/torrent/types/infohash"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateTorrents is the resolver for the validateTorrents field.
|
// TorrentDaemon is the resolver for the torrentDaemon field.
|
||||||
func (r *mutationResolver) ValidateTorrents(ctx context.Context, filter model.TorrentFilter) (bool, error) {
|
func (r *mutationResolver) TorrentDaemon(ctx context.Context) (*model.TorrentDaemonMutation, error) {
|
||||||
if filter.Infohash != nil {
|
return &model.TorrentDaemonMutation{}, nil
|
||||||
t, err := r.Resolver.Service.GetTorrent(*filter.Infohash)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if t == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
t.ValidateTorrent(ctx)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if filter.Everything != nil && *filter.Everything {
|
|
||||||
torrents, err := r.Resolver.Service.ListTorrents(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
for _, v := range torrents {
|
|
||||||
if err := v.ValidateTorrent(ctx); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanupTorrents is the resolver for the cleanupTorrents field.
|
|
||||||
func (r *mutationResolver) CleanupTorrents(ctx context.Context, files *bool, dryRun bool) (*model.CleanupResponse, error) {
|
|
||||||
torrents, err := r.Service.ListTorrents(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if files != nil && *files {
|
|
||||||
r, err := r.Service.Storage.CleanupFiles(ctx, torrents, dryRun)
|
|
||||||
return &model.CleanupResponse{
|
|
||||||
Count: int64(len(r)),
|
|
||||||
List: r,
|
|
||||||
}, err
|
|
||||||
} else {
|
|
||||||
r, err := r.Service.Storage.CleanupDirs(ctx, torrents, dryRun)
|
|
||||||
return &model.CleanupResponse{
|
|
||||||
Count: int64(len(r)),
|
|
||||||
List: r,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DownloadTorrent is the resolver for the downloadTorrent field.
|
|
||||||
func (r *mutationResolver) DownloadTorrent(ctx context.Context, infohash string, file *string) (*model.DownloadTorrentResponse, error) {
|
|
||||||
f := ""
|
|
||||||
if file != nil {
|
|
||||||
f = *file
|
|
||||||
}
|
|
||||||
|
|
||||||
err := r.Service.Download(ctx, &torrent.DownloadTask{
|
|
||||||
ID: uuid.New(),
|
|
||||||
InfoHash: aih.FromHexString(infohash),
|
|
||||||
File: f,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &model.DownloadTorrentResponse{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadFile is the resolver for the uploadFile field.
|
// UploadFile is the resolver for the uploadFile field.
|
||||||
|
|
|
@ -2,89 +2,18 @@ package resolver
|
||||||
|
|
||||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
// will be copied through when generating and any unknown code will be moved to the end.
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
// Code generated by github.com/99designs/gqlgen version v0.17.49
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/sources/torrent"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Torrents is the resolver for the torrents field.
|
// TorrentDaemon is the resolver for the torrentDaemon field.
|
||||||
func (r *queryResolver) Torrents(ctx context.Context, filter *model.TorrentsFilter) ([]*model.Torrent, error) {
|
func (r *queryResolver) TorrentDaemon(ctx context.Context) (*model.TorrentDaemonQuery, error) {
|
||||||
torrents, err := r.Service.ListTorrents(ctx)
|
return &model.TorrentDaemonQuery{}, nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
filterFuncs := []func(torrent *model.Torrent) bool{}
|
|
||||||
|
|
||||||
if filter != nil {
|
|
||||||
if filter.BytesCompleted != nil {
|
|
||||||
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
|
||||||
return filter.BytesCompleted.Include(torrent.BytesCompleted)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if filter.BytesMissing != nil {
|
|
||||||
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
|
||||||
return filter.BytesMissing.Include(torrent.BytesMissing)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if filter.PeersCount != nil {
|
|
||||||
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
|
||||||
return filter.PeersCount.Include(
|
|
||||||
int64(len(torrent.T.Torrent().PeerConns())),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if filter.Infohash != nil {
|
|
||||||
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
|
||||||
return filter.Infohash.Include(
|
|
||||||
torrent.Infohash,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if filter.Downloading != nil {
|
|
||||||
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
|
||||||
return filter.Downloading.Include(
|
|
||||||
torrent.Downloading,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
filterFunc := func(torrent *model.Torrent) bool {
|
|
||||||
for _, f := range filterFuncs {
|
|
||||||
if !f(torrent) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
tr := []*model.Torrent{}
|
|
||||||
for _, t := range torrents {
|
|
||||||
d, err := model.MapTorrent(ctx, t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !filterFunc(d) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
tr = append(tr, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
slices.SortStableFunc(torrents, func(t1, t2 *torrent.Controller) int {
|
|
||||||
return strings.Compare(t1.Name(), t2.Name())
|
|
||||||
})
|
|
||||||
|
|
||||||
return tr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FsEntry is the resolver for the fsEntry field.
|
// FsEntry is the resolver for the fsEntry field.
|
||||||
|
|
|
@ -2,7 +2,7 @@ package resolver
|
||||||
|
|
||||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
// will be copied through when generating and any unknown code will be moved to the end.
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
// Code generated by github.com/99designs/gqlgen version v0.17.49
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
90
src/delivery/graphql/resolver/torrent_mutation.resolvers.go
Normal file
90
src/delivery/graphql/resolver/torrent_mutation.resolvers.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
// Code generated by github.com/99designs/gqlgen version v0.17.49
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||||
|
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||||
|
"github.com/anacrolix/torrent/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateTorrent is the resolver for the validateTorrent field.
|
||||||
|
func (r *torrentDaemonMutationResolver) ValidateTorrent(ctx context.Context, obj *model.TorrentDaemonMutation, filter model.TorrentFilter) (bool, error) {
|
||||||
|
if filter.Infohash != nil {
|
||||||
|
t, err := r.Resolver.Service.GetTorrent(*filter.Infohash)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if t == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.ValidateTorrent(ctx)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Everything != nil && *filter.Everything {
|
||||||
|
torrents, err := r.Resolver.Service.ListTorrents(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, v := range torrents {
|
||||||
|
if err := v.ValidateTorrent(ctx); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTorrentPriority is the resolver for the setTorrentPriority field.
|
||||||
|
func (r *torrentDaemonMutationResolver) SetTorrentPriority(ctx context.Context, obj *model.TorrentDaemonMutation, infohash string, file *string, priority types.PiecePriority) (bool, error) {
|
||||||
|
t, err := r.Resolver.Service.GetTorrent(infohash)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if t == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = t.SetPriority(ctx, file, priority)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup is the resolver for the cleanup field.
|
||||||
|
func (r *torrentDaemonMutationResolver) Cleanup(ctx context.Context, obj *model.TorrentDaemonMutation, files *bool, dryRun bool) (*model.CleanupResponse, error) {
|
||||||
|
torrents, err := r.Service.ListTorrents(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if files != nil && *files {
|
||||||
|
r, err := r.Service.Storage.CleanupFiles(ctx, torrents, dryRun)
|
||||||
|
return &model.CleanupResponse{
|
||||||
|
Count: int64(len(r)),
|
||||||
|
List: r,
|
||||||
|
}, err
|
||||||
|
} else {
|
||||||
|
r, err := r.Service.Storage.CleanupDirs(ctx, torrents, dryRun)
|
||||||
|
return &model.CleanupResponse{
|
||||||
|
Count: int64(len(r)),
|
||||||
|
List: r,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TorrentDaemonMutation returns graph.TorrentDaemonMutationResolver implementation.
|
||||||
|
func (r *Resolver) TorrentDaemonMutation() graph.TorrentDaemonMutationResolver {
|
||||||
|
return &torrentDaemonMutationResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
type torrentDaemonMutationResolver struct{ *Resolver }
|
94
src/delivery/graphql/resolver/torrent_query.resolvers.go
Normal file
94
src/delivery/graphql/resolver/torrent_query.resolvers.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
// Code generated by github.com/99designs/gqlgen version v0.17.49
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||||
|
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||||
|
"git.kmsign.ru/royalcat/tstor/src/sources/torrent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Torrents is the resolver for the torrents field.
|
||||||
|
func (r *torrentDaemonQueryResolver) Torrents(ctx context.Context, obj *model.TorrentDaemonQuery, filter *model.TorrentsFilter) ([]*model.Torrent, error) {
|
||||||
|
torrents, err := r.Service.ListTorrents(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
filterFuncs := []func(torrent *model.Torrent) bool{}
|
||||||
|
|
||||||
|
if filter != nil {
|
||||||
|
if filter.BytesCompleted != nil {
|
||||||
|
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
||||||
|
return filter.BytesCompleted.Include(torrent.BytesCompleted)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if filter.BytesMissing != nil {
|
||||||
|
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
||||||
|
return filter.BytesMissing.Include(torrent.BytesMissing)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if filter.PeersCount != nil {
|
||||||
|
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
||||||
|
return filter.PeersCount.Include(
|
||||||
|
int64(len(torrent.T.Torrent().PeerConns())),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if filter.Infohash != nil {
|
||||||
|
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
||||||
|
return filter.Infohash.Include(
|
||||||
|
torrent.Infohash,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if filter.Priority != nil {
|
||||||
|
filterFuncs = append(filterFuncs, func(torrent *model.Torrent) bool {
|
||||||
|
return filter.Priority.Include(
|
||||||
|
torrent.Priority,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterFunc := func(torrent *model.Torrent) bool {
|
||||||
|
for _, f := range filterFuncs {
|
||||||
|
if !f(torrent) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
tr := []*model.Torrent{}
|
||||||
|
for _, t := range torrents {
|
||||||
|
d, err := model.MapTorrent(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !filterFunc(d) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tr = append(tr, d)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortStableFunc(torrents, func(t1, t2 *torrent.Controller) int {
|
||||||
|
return strings.Compare(t1.Name(), t2.Name())
|
||||||
|
})
|
||||||
|
|
||||||
|
return tr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TorrentDaemonQuery returns graph.TorrentDaemonQueryResolver implementation.
|
||||||
|
func (r *Resolver) TorrentDaemonQuery() graph.TorrentDaemonQueryResolver {
|
||||||
|
return &torrentDaemonQueryResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
type torrentDaemonQueryResolver struct{ *Resolver }
|
|
@ -2,13 +2,14 @@ package resolver
|
||||||
|
|
||||||
// This file will be automatically regenerated based on the schema, any resolver implementations
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
// will be copied through when generating and any unknown code will be moved to the end.
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
// Code generated by github.com/99designs/gqlgen version v0.17.45
|
// Code generated by github.com/99designs/gqlgen version v0.17.49
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||||
|
"github.com/anacrolix/torrent/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Name is the resolver for the name field.
|
// Name is the resolver for the name field.
|
||||||
|
@ -16,6 +17,11 @@ func (r *torrentResolver) Name(ctx context.Context, obj *model.Torrent) (string,
|
||||||
return obj.T.Name(), nil
|
return obj.T.Name(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Priority is the resolver for the priority field.
|
||||||
|
func (r *torrentResolver) Priority(ctx context.Context, obj *model.Torrent) (types.PiecePriority, error) {
|
||||||
|
return obj.T.Priority(ctx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Files is the resolver for the files field.
|
// Files is the resolver for the files field.
|
||||||
func (r *torrentResolver) Files(ctx context.Context, obj *model.Torrent) ([]*model.TorrentFile, error) {
|
func (r *torrentResolver) Files(ctx context.Context, obj *model.Torrent) ([]*model.TorrentFile, error) {
|
||||||
out := []*model.TorrentFile{}
|
out := []*model.TorrentFile{}
|
|
@ -18,6 +18,10 @@ import (
|
||||||
"github.com/ravilushqa/otelgqlgen"
|
"github.com/ravilushqa/otelgqlgen"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func noopDirective(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
|
||||||
|
return next(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
func GraphQLHandler(service *torrent.Daemon, vfs vfs.Filesystem) http.Handler {
|
func GraphQLHandler(service *torrent.Daemon, vfs vfs.Filesystem) http.Handler {
|
||||||
graphqlHandler := handler.NewDefaultServer(
|
graphqlHandler := handler.NewDefaultServer(
|
||||||
graph.NewExecutableSchema(
|
graph.NewExecutableSchema(
|
||||||
|
@ -25,6 +29,8 @@ func GraphQLHandler(service *torrent.Daemon, vfs vfs.Filesystem) http.Handler {
|
||||||
Resolvers: &resolver.Resolver{Service: service, VFS: vfs},
|
Resolvers: &resolver.Resolver{Service: service, VFS: vfs},
|
||||||
Directives: graph.DirectiveRoot{
|
Directives: graph.DirectiveRoot{
|
||||||
OneOf: graph.OneOf,
|
OneOf: graph.OneOf,
|
||||||
|
Resolver: noopDirective,
|
||||||
|
Stream: noopDirective,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
||||||
|
@ -11,14 +11,14 @@ import (
|
||||||
|
|
||||||
func BadgerLogger(name ...string) badger.Logger {
|
func BadgerLogger(name ...string) badger.Logger {
|
||||||
return &badgerLogger{
|
return &badgerLogger{
|
||||||
L: rlog.Component(append(name, "badger")...).Slog(),
|
L: rlog.Component(append([]string{"badger"}, name...)...).Nested(2),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ badger.Logger = (*badgerLogger)(nil)
|
var _ badger.Logger = (*badgerLogger)(nil)
|
||||||
|
|
||||||
type badgerLogger struct {
|
type badgerLogger struct {
|
||||||
L *slog.Logger
|
L *rlog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func fmtBadgerLog(m string, f ...any) string {
|
func fmtBadgerLog(m string, f ...any) string {
|
||||||
|
@ -26,17 +26,21 @@ func fmtBadgerLog(m string, f ...any) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *badgerLogger) Errorf(m string, f ...interface{}) {
|
func (l *badgerLogger) Errorf(m string, f ...interface{}) {
|
||||||
l.L.Error(fmtBadgerLog(m, f...))
|
ctx := context.Background()
|
||||||
|
l.L.Error(ctx, fmtBadgerLog(m, f...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *badgerLogger) Warningf(m string, f ...interface{}) {
|
func (l *badgerLogger) Warningf(m string, f ...interface{}) {
|
||||||
l.L.Warn(fmtBadgerLog(m, f...))
|
ctx := context.Background()
|
||||||
|
l.L.Warn(ctx, fmtBadgerLog(m, f...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *badgerLogger) Infof(m string, f ...interface{}) {
|
func (l *badgerLogger) Infof(m string, f ...interface{}) {
|
||||||
l.L.Info(fmtBadgerLog(m, f...))
|
ctx := context.Background()
|
||||||
|
l.L.Info(ctx, fmtBadgerLog(m, f...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *badgerLogger) Debugf(m string, f ...interface{}) {
|
func (l *badgerLogger) Debugf(m string, f ...interface{}) {
|
||||||
l.L.Debug(fmtBadgerLog(m, f...))
|
ctx := context.Background()
|
||||||
|
l.L.Debug(ctx, fmtBadgerLog(m, f...))
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,21 @@ func (s *Controller) Files(ctx context.Context) ([]*torrent.File, error) {
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Controller) GetFile(ctx context.Context, file string) (*torrent.File, error) {
|
||||||
|
files, err := s.Files(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range files {
|
||||||
|
if v.Path() == file {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func Map[T, U any](ts []T, f func(T) U) []U {
|
func Map[T, U any](ts []T, f func(T) U) []U {
|
||||||
us := make([]U, len(ts))
|
us := make([]U, len(ts))
|
||||||
for i := range ts {
|
for i := range ts {
|
||||||
|
@ -166,18 +181,79 @@ func (s *Controller) ValidateTorrent(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) SetFilePriority(ctx context.Context, file *torrent.File, priority types.PiecePriority) error {
|
func (c *Controller) SetPriority(ctx context.Context, filePath *string, priority types.PiecePriority) error {
|
||||||
log := c.log.With(slog.String("file", file.Path()), slog.Int("priority", int(priority)))
|
log := c.log.With(slog.Int("priority", int(priority)))
|
||||||
log.Info(ctx, "set pritority for file")
|
|
||||||
|
|
||||||
|
if filePath != nil {
|
||||||
|
file, err := c.GetFile(ctx, *filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if file == nil {
|
||||||
|
log.Error(ctx, "file not found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.setFilePriority(ctx, file, priority)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range c.t.Files() {
|
||||||
|
err := c.setFilePriority(ctx, f, priority)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPriority = types.PiecePriorityNone
|
||||||
|
|
||||||
|
func (c *Controller) Priority(ctx context.Context, filePath *string) (types.PiecePriority, error) {
|
||||||
|
if filePath == nil {
|
||||||
|
prio := defaultPriority
|
||||||
|
err := c.fileProperties.Range(ctx, func(filePath string, v FileProperties) error {
|
||||||
|
if filePath == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Priority > prio {
|
||||||
|
prio = v.Priority
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err == kv.ErrKeyNotFound {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return prio, err
|
||||||
|
}
|
||||||
|
|
||||||
|
props, err := c.fileProperties.Get(ctx, *filePath)
|
||||||
|
if err != nil {
|
||||||
|
if err == kv.ErrKeyNotFound {
|
||||||
|
return defaultPriority, nil
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.Priority, nil
|
||||||
|
}
|
||||||
|
func (c *Controller) setFilePriority(ctx context.Context, file *torrent.File, priority types.PiecePriority) error {
|
||||||
err := c.fileProperties.Edit(ctx, file.Path(), func(ctx context.Context, v FileProperties) (FileProperties, error) {
|
err := c.fileProperties.Edit(ctx, file.Path(), func(ctx context.Context, v FileProperties) (FileProperties, error) {
|
||||||
v.Priority = priority
|
v.Priority = priority
|
||||||
return v, nil
|
return v, nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err == kv.ErrKeyNotFound {
|
||||||
|
err := c.fileProperties.Set(ctx, file.Path(), FileProperties{Priority: priority})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
file.SetPriority(priority)
|
file.SetPriority(priority)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -204,7 +280,6 @@ func (c *Controller) initializeTorrentPriories(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
file.SetPriority(props.Priority)
|
file.SetPriority(props.Priority)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(ctx, "torrent initialization complete", slog.String("infohash", c.InfoHash()), slog.String("torrent_name", c.Name()))
|
log.Info(ctx, "torrent initialization complete", slog.String("infohash", c.InfoHash()), slog.String("torrent_name", c.Name()))
|
||||||
|
|
|
@ -43,21 +43,26 @@ func newPieceCompletion(dir string) (storage.PieceCompletion, error) {
|
||||||
return &badgerPieceCompletion{db}, nil
|
return &badgerPieceCompletion{db}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const delimeter rune = 0x1F
|
||||||
|
|
||||||
func pkToBytes(pk metainfo.PieceKey) []byte {
|
func pkToBytes(pk metainfo.PieceKey) []byte {
|
||||||
key := make([]byte, len(pk.InfoHash.Bytes()))
|
key := make([]byte, 0, len(pk.InfoHash.Bytes())+1+4)
|
||||||
copy(key, pk.InfoHash.Bytes())
|
key = append(key, pk.InfoHash.Bytes()...)
|
||||||
binary.BigEndian.AppendUint32(key, uint32(pk.Index))
|
key = append(key, byte(delimeter))
|
||||||
|
key = binary.BigEndian.AppendUint32(key, uint32(pk.Index))
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *badgerPieceCompletion) Get(pk metainfo.PieceKey) (storage.Completion, error) {
|
func (k *badgerPieceCompletion) Get(pk metainfo.PieceKey) (storage.Completion, error) {
|
||||||
completion := storage.Completion{
|
completion := storage.Completion{
|
||||||
Ok: true,
|
Complete: false,
|
||||||
|
Ok: false,
|
||||||
}
|
}
|
||||||
err := k.db.View(func(tx *badger.Txn) error {
|
err := k.db.View(func(tx *badger.Txn) error {
|
||||||
item, err := tx.Get(pkToBytes(pk))
|
item, err := tx.Get(pkToBytes(pk))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == badger.ErrKeyNotFound {
|
if err == badger.ErrKeyNotFound {
|
||||||
|
completion.Complete = false
|
||||||
completion.Ok = false
|
completion.Ok = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -71,11 +76,12 @@ func (k *badgerPieceCompletion) Get(pk metainfo.PieceKey) (storage.Completion, e
|
||||||
}
|
}
|
||||||
compl := PieceCompletionState(valCopy[0])
|
compl := PieceCompletionState(valCopy[0])
|
||||||
|
|
||||||
completion.Ok = true
|
|
||||||
switch compl {
|
switch compl {
|
||||||
case PieceComplete:
|
case PieceComplete:
|
||||||
|
completion.Ok = true
|
||||||
completion.Complete = true
|
completion.Complete = true
|
||||||
case PieceNotComplete:
|
case PieceNotComplete:
|
||||||
|
completion.Ok = true
|
||||||
completion.Complete = false
|
completion.Complete = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,12 @@ func setupStorage(cfg config.TorrentClient) (*fileStorage, storage.PieceCompleti
|
||||||
if err := os.MkdirAll(pcp, 0744); err != nil {
|
if err := os.MkdirAll(pcp, 0744); err != nil {
|
||||||
return nil, nil, fmt.Errorf("error creating piece completion folder: %w", err)
|
return nil, nil, fmt.Errorf("error creating piece completion folder: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pc, err := storage.NewBoltPieceCompletion(pcp)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
pc, err := newPieceCompletion(pcp)
|
pc, err := newPieceCompletion(pcp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("error creating servers piece completion: %w", err)
|
return nil, nil, fmt.Errorf("error creating servers piece completion: %w", err)
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -22,34 +21,38 @@ import (
|
||||||
// OpenTorrent implements storage.ClientImplCloser.
|
// OpenTorrent implements storage.ClientImplCloser.
|
||||||
func (me *fileStorage) OpenTorrent(info *metainfo.Info, infoHash infohash.T) (storage.TorrentImpl, error) {
|
func (me *fileStorage) OpenTorrent(info *metainfo.Info, infoHash infohash.T) (storage.TorrentImpl, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
log := me.log
|
log := me.log.With(slog.String("infohash", infoHash.HexString()))
|
||||||
|
|
||||||
dir := torrentDir(me.baseDir, infoHash)
|
// dir := torrentDir(me.baseDir, infoHash)
|
||||||
legacyDir := filepath.Join(me.baseDir, info.Name)
|
// legacyDir := filepath.Join(me.baseDir, info.Name)
|
||||||
|
|
||||||
log = log.With(slog.String("legacy_dir", legacyDir), slog.String("dir", dir))
|
// log = log.With(slog.String("legacy_dir", legacyDir), slog.String("dir", dir))
|
||||||
if _, err := os.Stat(legacyDir); err == nil {
|
// if _, err := os.Stat(legacyDir); err == nil {
|
||||||
log.Warn(ctx, "legacy torrent dir found, renaming", slog.String("dir", dir))
|
// log.Warn(ctx, "legacy torrent dir found, renaming", slog.String("dir", dir))
|
||||||
err = os.Rename(legacyDir, dir)
|
// err = os.Rename(legacyDir, dir)
|
||||||
|
// if err != nil {
|
||||||
|
// return storage.TorrentImpl{}, fmt.Errorf("error renaming legacy torrent dir: %w", err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if _, err := os.Stat(dir); errors.Is(err, fs.ErrNotExist) {
|
||||||
|
// log.Info(ctx, "new torrent, trying copy files from existing")
|
||||||
|
// dups := me.dupIndex.Includes(infoHash, info.Files)
|
||||||
|
|
||||||
|
// for _, dup := range dups {
|
||||||
|
// err := me.copyDup(ctx, infoHash, dup)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Error(ctx, "error copying file", slog.String("file", dup.fileinfo.DisplayPath(info)), rlog.Error(err))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
impl, err := me.client.OpenTorrent(info, infoHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return storage.TorrentImpl{}, fmt.Errorf("error renaming legacy torrent dir: %w", err)
|
log.Error(ctx, "error opening torrent", rlog.Error(err))
|
||||||
}
|
}
|
||||||
}
|
return impl, err
|
||||||
|
|
||||||
if _, err := os.Stat(dir); errors.Is(err, fs.ErrNotExist) {
|
|
||||||
log.Info(ctx, "new torrent, trying copy files from existing")
|
|
||||||
dups := me.dupIndex.Includes(infoHash, info.Files)
|
|
||||||
|
|
||||||
for _, dup := range dups {
|
|
||||||
err := me.copyDup(ctx, infoHash, dup)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(ctx, "error copying file", slog.String("file", dup.fileinfo.DisplayPath(info)), rlog.Error(err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return me.client.OpenTorrent(info, infoHash)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me *fileStorage) copyDup(ctx context.Context, infoHash infohash.T, dup dupInfo) error {
|
func (me *fileStorage) copyDup(ctx context.Context, infoHash infohash.T, dup dupInfo) error {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
|
directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION
|
||||||
|
directive @resolver on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
||||||
directive @stream on FIELD_DEFINITION
|
directive @stream on FIELD_DEFINITION
|
||||||
type ArchiveFS implements Dir & FsEntry {
|
type ArchiveFS implements Dir & FsEntry {
|
||||||
name: String!
|
name: String!
|
||||||
entries: [FsEntry!]!
|
entries: [FsEntry!]! @resolver
|
||||||
size: Int!
|
size: Int!
|
||||||
}
|
}
|
||||||
input BooleanFilter @oneOf {
|
input BooleanFilter @oneOf {
|
||||||
|
@ -43,9 +44,7 @@ input IntFilter @oneOf {
|
||||||
in: [Int!]
|
in: [Int!]
|
||||||
}
|
}
|
||||||
type Mutation {
|
type Mutation {
|
||||||
validateTorrents(filter: TorrentFilter!): Boolean!
|
torrentDaemon: TorrentDaemonMutation @resolver
|
||||||
cleanupTorrents(files: Boolean, dryRun: Boolean!): CleanupResponse!
|
|
||||||
downloadTorrent(infohash: String!, file: String): DownloadTorrentResponse
|
|
||||||
uploadFile(dir: String!, file: Upload!): Boolean!
|
uploadFile(dir: String!, file: Upload!): Boolean!
|
||||||
dedupeStorage: Int!
|
dedupeStorage: Int!
|
||||||
}
|
}
|
||||||
|
@ -58,12 +57,12 @@ interface Progress {
|
||||||
total: Int!
|
total: Int!
|
||||||
}
|
}
|
||||||
type Query {
|
type Query {
|
||||||
torrents(filter: TorrentsFilter): [Torrent!]!
|
torrentDaemon: TorrentDaemonQuery @resolver
|
||||||
fsEntry(path: String!): FsEntry
|
fsEntry(path: String!): FsEntry
|
||||||
}
|
}
|
||||||
type ResolverFS implements Dir & FsEntry {
|
type ResolverFS implements Dir & FsEntry {
|
||||||
name: String!
|
name: String!
|
||||||
entries: [FsEntry!]!
|
entries: [FsEntry!]! @resolver
|
||||||
}
|
}
|
||||||
type Schema {
|
type Schema {
|
||||||
query: Query
|
query: Query
|
||||||
|
@ -71,7 +70,7 @@ type Schema {
|
||||||
}
|
}
|
||||||
type SimpleDir implements Dir & FsEntry {
|
type SimpleDir implements Dir & FsEntry {
|
||||||
name: String!
|
name: String!
|
||||||
entries: [FsEntry!]!
|
entries: [FsEntry!]! @resolver
|
||||||
}
|
}
|
||||||
type SimpleFile implements File & FsEntry {
|
type SimpleFile implements File & FsEntry {
|
||||||
name: String!
|
name: String!
|
||||||
|
@ -90,20 +89,28 @@ type Task {
|
||||||
id: ID!
|
id: ID!
|
||||||
}
|
}
|
||||||
type Torrent {
|
type Torrent {
|
||||||
name: String!
|
name: String! @resolver
|
||||||
infohash: String!
|
infohash: String!
|
||||||
bytesCompleted: Int!
|
bytesCompleted: Int!
|
||||||
torrentFilePath: String!
|
torrentFilePath: String!
|
||||||
bytesMissing: Int!
|
bytesMissing: Int!
|
||||||
files: [TorrentFile!]!
|
priority: TorrentPriority! @resolver
|
||||||
excludedFiles: [TorrentFile!]!
|
files: [TorrentFile!]! @resolver
|
||||||
peers: [TorrentPeer!]!
|
excludedFiles: [TorrentFile!]! @resolver
|
||||||
downloading: Boolean!
|
peers: [TorrentPeer!]! @resolver
|
||||||
|
}
|
||||||
|
type TorrentDaemonMutation {
|
||||||
|
validateTorrent(filter: TorrentFilter!): Boolean! @resolver
|
||||||
|
setTorrentPriority(infohash: String!, file: String, priority: TorrentPriority!): Boolean! @resolver
|
||||||
|
cleanup(files: Boolean, dryRun: Boolean!): CleanupResponse! @resolver
|
||||||
|
}
|
||||||
|
type TorrentDaemonQuery {
|
||||||
|
torrents(filter: TorrentsFilter): [Torrent!]! @resolver
|
||||||
}
|
}
|
||||||
type TorrentFS implements Dir & FsEntry {
|
type TorrentFS implements Dir & FsEntry {
|
||||||
name: String!
|
name: String!
|
||||||
torrent: Torrent!
|
torrent: Torrent!
|
||||||
entries: [FsEntry!]!
|
entries: [FsEntry!]! @resolver
|
||||||
}
|
}
|
||||||
type TorrentFile {
|
type TorrentFile {
|
||||||
filename: String!
|
filename: String!
|
||||||
|
@ -126,6 +133,22 @@ type TorrentPeer {
|
||||||
port: Int!
|
port: Int!
|
||||||
clientName: String!
|
clientName: String!
|
||||||
}
|
}
|
||||||
|
enum TorrentPriority {
|
||||||
|
NONE
|
||||||
|
NORMAL
|
||||||
|
HIGH
|
||||||
|
READAHEAD
|
||||||
|
NEXT
|
||||||
|
NOW
|
||||||
|
}
|
||||||
|
input TorrentPriorityFilter @oneOf {
|
||||||
|
eq: TorrentPriority
|
||||||
|
gt: TorrentPriority
|
||||||
|
lt: TorrentPriority
|
||||||
|
gte: TorrentPriority
|
||||||
|
lte: TorrentPriority
|
||||||
|
in: [TorrentPriority!]
|
||||||
|
}
|
||||||
type TorrentProgress implements Progress {
|
type TorrentProgress implements Progress {
|
||||||
torrent: Torrent!
|
torrent: Torrent!
|
||||||
current: Int!
|
current: Int!
|
||||||
|
@ -137,6 +160,6 @@ input TorrentsFilter {
|
||||||
bytesCompleted: IntFilter
|
bytesCompleted: IntFilter
|
||||||
bytesMissing: IntFilter
|
bytesMissing: IntFilter
|
||||||
peersCount: IntFilter
|
peersCount: IntFilter
|
||||||
downloading: BooleanFilter
|
priority: TorrentPriorityFilter
|
||||||
}
|
}
|
||||||
scalar Upload
|
scalar Upload
|
||||||
|
|
Loading…
Reference in a new issue