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:
|
||||
model: github.com/99designs/gqlgen/graphql.Int64
|
||||
Torrent:
|
||||
fields:
|
||||
name:
|
||||
resolver: true
|
||||
files:
|
||||
resolver: true
|
||||
excludedFiles:
|
||||
resolver: true
|
||||
peers:
|
||||
resolver: true
|
||||
extraFields:
|
||||
T:
|
||||
type: "*git.kmsign.ru/royalcat/tstor/src/host/torrent.Controller"
|
||||
type: "*git.kmsign.ru/royalcat/tstor/src/sources/torrent.Controller"
|
||||
TorrentFile:
|
||||
extraFields:
|
||||
F:
|
||||
|
@ -43,32 +34,37 @@ models:
|
|||
F:
|
||||
type: "*github.com/anacrolix/torrent.PeerConn"
|
||||
SimpleDir:
|
||||
fields:
|
||||
entries:
|
||||
resolver: true
|
||||
extraFields:
|
||||
Path:
|
||||
type: string
|
||||
FS:
|
||||
type: "git.kmsign.ru/royalcat/tstor/src/vfs.Filesystem"
|
||||
TorrentFS:
|
||||
fields:
|
||||
entries:
|
||||
resolver: true
|
||||
extraFields:
|
||||
FS:
|
||||
type: "*git.kmsign.ru/royalcat/tstor/src/host/torrent.TorrentFS"
|
||||
type: "*git.kmsign.ru/royalcat/tstor/src/sources/torrent.TorrentFS"
|
||||
ResolverFS:
|
||||
fields:
|
||||
entries:
|
||||
resolver: true
|
||||
extraFields:
|
||||
FS:
|
||||
type: "*git.kmsign.ru/royalcat/tstor/src/vfs.ResolverFS"
|
||||
ArchiveFS:
|
||||
fields:
|
||||
entries:
|
||||
resolver: true
|
||||
extraFields:
|
||||
FS:
|
||||
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",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch file",
|
||||
"name": "Generate GraphQL",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "debug",
|
||||
"program": "${file}"
|
||||
"program": "${workspaceFolder}/cmd/generate-graphql/main.go",
|
||||
},
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"name": "TStor",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.22 as builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.22 as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
@ -14,7 +14,9 @@ COPY ./templates ./templates
|
|||
COPY embed.go embed.go
|
||||
|
||||
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
|
||||
|
|
2
Makefile
2
Makefile
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
|
||||
ui/lib/api/schema.graphql: src/delivery/graphql/* cmd/generate-graphql-schema/*
|
||||
|
|
|
@ -10,6 +10,23 @@ import (
|
|||
"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 {
|
||||
}
|
||||
|
||||
|
@ -25,9 +42,6 @@ func (fieldDirectiveFix) GenerateCode(cfg *codegen.Data) error {
|
|||
for _, v := range field.TypeReference.Definition.Directives {
|
||||
directiveMap[v.Name]++
|
||||
}
|
||||
// for _, v := range field.Object.Directives {
|
||||
// directiveMap[v.Name]++
|
||||
// }
|
||||
|
||||
directive := make([]*codegen.Directive, 0, len(field.Directives))
|
||||
for _, v := range field.Directives {
|
||||
|
@ -48,18 +62,33 @@ func (fieldDirectiveFix) GenerateCode(cfg *codegen.Data) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
cfg, err := config.LoadConfigFromDefaultLocations()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
|
||||
os.Exit(2)
|
||||
type resolverDirective struct {
|
||||
}
|
||||
|
||||
func (resolverDirective) Name() string {
|
||||
return "Resolver directive support"
|
||||
}
|
||||
|
||||
func (resolverDirective) GenerateCode(cfg *codegen.Data) error {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = api.Generate(cfg,
|
||||
api.AddPlugin(&fieldDirectiveFix{}),
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(3)
|
||||
}
|
||||
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
|
||||
|
||||
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-logs-go v0.5.0
|
||||
github.com/anacrolix/dht/v2 v2.21.1
|
||||
github.com/anacrolix/log v0.15.2
|
||||
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/bodgit/sevenzip v1.5.1
|
||||
github.com/cyphar/filepath-securejoin v0.2.5
|
||||
|
@ -43,7 +43,7 @@ require (
|
|||
github.com/samber/slog-zerolog v1.0.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
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/memphis v0.0.0-20210922141505-529d4987ab7e
|
||||
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/trace v1.27.0
|
||||
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/sys v0.20.0
|
||||
golang.org/x/sys v0.21.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -168,7 +168,7 @@ require (
|
|||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // 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/tidwall/btree v1.7.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
|
@ -185,11 +185,11 @@ require (
|
|||
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
golang.org/x/crypto v0.24.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/text v0.16.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/rpc v0.0.0-20240515191416-fc5f0ca64291 // 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=
|
||||
filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU=
|
||||
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.45/go.mod h1:Bas0XQ+Jiu/Xm5E33jC8sES3G+iC2esHBMXcq0fUPs0=
|
||||
github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ=
|
||||
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/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
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 v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||
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.0/go.mod h1:5DMHbeIM1TuC5wTQ99XieKKLiYZYz6iB2lyZpKZEr6w=
|
||||
github.com/anacrolix/torrent v1.56.1 h1:QeJMOP0NuhpQ5dATsOqEL0vUO85aPMNMGP2FACNt0Eg=
|
||||
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/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
|
||||
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.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||
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.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||
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 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
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/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
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.11/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc=
|
||||
github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8=
|
||||
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/go.mod h1:/qe02xr3jvTUz8u/PV0FHGpP8t96OQNP7U9BJMwMLEw=
|
||||
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.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.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
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.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
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-20180826012351-8a410e7b638d/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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
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.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
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-20190226205417-e64efc72b421/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.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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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.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.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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.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.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
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-20191011141410-1b5146add898/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 {
|
||||
validateTorrents(filter: TorrentFilter!): Boolean!
|
||||
cleanupTorrents(files: Boolean, dryRun: Boolean!): CleanupResponse!
|
||||
downloadTorrent(infohash: String!, file: String): DownloadTorrentResponse
|
||||
torrentDaemon: TorrentDaemonMutation @resolver
|
||||
|
||||
uploadFile(dir: String!, file: Upload!): Boolean!
|
||||
dedupeStorage: Int!
|
||||
}
|
||||
|
||||
input TorrentFilter @oneOf {
|
||||
everything: Boolean
|
||||
infohash: String
|
||||
# pathGlob: String!
|
||||
}
|
||||
|
||||
type DownloadTorrentResponse {
|
||||
task: Task
|
||||
}
|
||||
|
||||
type CleanupResponse {
|
||||
count: Int!
|
||||
list: [String!]!
|
||||
}
|
||||
|
||||
type Task {
|
||||
id: ID!
|
||||
}
|
||||
|
|
|
@ -1,46 +1,5 @@
|
|||
type Query {
|
||||
torrents(filter: TorrentsFilter): [Torrent!]!
|
||||
torrentDaemon: TorrentDaemonQuery @resolver
|
||||
|
||||
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 @resolver on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
||||
|
||||
directive @stream on FIELD_DEFINITION
|
||||
|
||||
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 {
|
||||
name: String!
|
||||
name: String! @resolver
|
||||
infohash: String!
|
||||
bytesCompleted: Int!
|
||||
torrentFilePath: String!
|
||||
bytesMissing: Int!
|
||||
files: [TorrentFile!]!
|
||||
excludedFiles: [TorrentFile!]!
|
||||
peers: [TorrentPeer!]!
|
||||
|
||||
# if at least one piece of the torrent is request to download and not already downloaded
|
||||
downloading: Boolean!
|
||||
priority: TorrentPriority! @resolver
|
||||
files: [TorrentFile!]! @resolver
|
||||
excludedFiles: [TorrentFile!]! @resolver
|
||||
peers: [TorrentPeer!]! @resolver
|
||||
}
|
||||
|
||||
type TorrentFile {
|
||||
|
@ -25,3 +23,11 @@ type TorrentPeer {
|
|||
port: Int!
|
||||
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 {
|
||||
name: String!
|
||||
entries: [FsEntry!]!
|
||||
entries: [FsEntry!]! @resolver
|
||||
}
|
||||
|
||||
type SimpleFile implements File & FsEntry {
|
||||
|
@ -24,12 +24,12 @@ type SimpleFile implements File & FsEntry {
|
|||
|
||||
type ResolverFS implements Dir & FsEntry {
|
||||
name: String!
|
||||
entries: [FsEntry!]!
|
||||
entries: [FsEntry!]! @resolver
|
||||
}
|
||||
|
||||
type ArchiveFS implements Dir & FsEntry {
|
||||
name: String!
|
||||
entries: [FsEntry!]!
|
||||
entries: [FsEntry!]! @resolver
|
||||
|
||||
size: Int!
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ type ArchiveFS implements Dir & FsEntry {
|
|||
type TorrentFS implements Dir & FsEntry {
|
||||
name: String!
|
||||
torrent: Torrent!
|
||||
entries: [FsEntry!]!
|
||||
entries: [FsEntry!]! @resolver
|
||||
}
|
||||
|
||||
type TorrentFileEntry implements File & FsEntry {
|
||||
|
|
|
@ -34,24 +34,35 @@ func AddHandler(nh slog.Handler) {
|
|||
}
|
||||
|
||||
type Logger struct {
|
||||
handler slog.Handler
|
||||
component []string
|
||||
handler slog.Handler
|
||||
callNesting int
|
||||
component []string
|
||||
}
|
||||
|
||||
const functionKey = "function"
|
||||
|
||||
const componentKey = "component"
|
||||
const componentSep = "."
|
||||
|
||||
func (l *Logger) log(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr) {
|
||||
var pcs [1]uintptr
|
||||
runtime.Callers(3, pcs[:])
|
||||
runtime.Callers(3+l.callNesting, pcs[:])
|
||||
pc := pcs[0]
|
||||
f := runtime.FuncForPC(pc)
|
||||
attrs = append(attrs, slog.String(functionKey, f.Name()))
|
||||
if f != nil {
|
||||
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.AddAttrs(attrs...)
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
_ = 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...)
|
||||
}
|
||||
|
||||
const componentKey = "component"
|
||||
const componentSep = "."
|
||||
|
||||
func (log *Logger) WithComponent(name string) *Logger {
|
||||
c := append(log.component, name)
|
||||
return &Logger{
|
||||
handler: log.handler.WithAttrs([]slog.Attr{
|
||||
slog.String(componentKey, strings.Join(c, componentSep)),
|
||||
}),
|
||||
component: c,
|
||||
handler: log.handler,
|
||||
component: append(log.component, name),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// TODO currently not logging function name
|
||||
func (l *Logger) Slog() *slog.Logger {
|
||||
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"
|
||||
|
||||
func Error(err error) slog.Attr {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3,6 +3,8 @@ package model
|
|||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/anacrolix/torrent/types"
|
||||
)
|
||||
|
||||
type Filter[T any] interface {
|
||||
|
@ -52,3 +54,23 @@ func (f *BooleanFilter) Include(v bool) bool {
|
|||
|
||||
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) {
|
||||
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{
|
||||
Infohash: t.InfoHash(),
|
||||
Name: t.Name(),
|
||||
BytesCompleted: t.BytesCompleted(),
|
||||
BytesMissing: t.BytesMissing(),
|
||||
T: t,
|
||||
Downloading: downloading,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"git.kmsign.ru/royalcat/tstor/src/sources/torrent"
|
||||
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
||||
torrent1 "github.com/anacrolix/torrent"
|
||||
"github.com/anacrolix/torrent/types"
|
||||
)
|
||||
|
||||
type Dir interface {
|
||||
|
@ -176,13 +177,23 @@ type Torrent struct {
|
|||
BytesCompleted int64 `json:"bytesCompleted"`
|
||||
TorrentFilePath string `json:"torrentFilePath"`
|
||||
BytesMissing int64 `json:"bytesMissing"`
|
||||
Priority types.PiecePriority `json:"priority"`
|
||||
Files []*TorrentFile `json:"files"`
|
||||
ExcludedFiles []*TorrentFile `json:"excludedFiles"`
|
||||
Peers []*TorrentPeer `json:"peers"`
|
||||
Downloading bool `json:"downloading"`
|
||||
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 {
|
||||
Name string `json:"name"`
|
||||
Torrent *Torrent `json:"torrent"`
|
||||
|
@ -238,6 +249,15 @@ type TorrentPeer struct {
|
|||
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 {
|
||||
Torrent *Torrent `json:"torrent"`
|
||||
Current int64 `json:"current"`
|
||||
|
@ -249,10 +269,10 @@ func (this TorrentProgress) GetCurrent() int64 { return this.Current }
|
|||
func (this TorrentProgress) GetTotal() int64 { return this.Total }
|
||||
|
||||
type TorrentsFilter struct {
|
||||
Infohash *StringFilter `json:"infohash,omitempty"`
|
||||
Name *StringFilter `json:"name,omitempty"`
|
||||
BytesCompleted *IntFilter `json:"bytesCompleted,omitempty"`
|
||||
BytesMissing *IntFilter `json:"bytesMissing,omitempty"`
|
||||
PeersCount *IntFilter `json:"peersCount,omitempty"`
|
||||
Downloading *BooleanFilter `json:"downloading,omitempty"`
|
||||
Infohash *StringFilter `json:"infohash,omitempty"`
|
||||
Name *StringFilter `json:"name,omitempty"`
|
||||
BytesCompleted *IntFilter `json:"bytesCompleted,omitempty"`
|
||||
BytesMissing *IntFilter `json:"bytesMissing,omitempty"`
|
||||
PeersCount *IntFilter `json:"peersCount,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
|
||||
// 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 (
|
||||
"context"
|
||||
|
|
|
@ -2,7 +2,7 @@ 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.45
|
||||
// Code generated by github.com/99designs/gqlgen version v0.17.49
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -11,84 +11,14 @@ import (
|
|||
"os"
|
||||
pathlib "path"
|
||||
|
||||
"git.kmsign.ru/royalcat/tstor/pkg/uuid"
|
||||
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"
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
aih "github.com/anacrolix/torrent/types/infohash"
|
||||
)
|
||||
|
||||
// ValidateTorrents is the resolver for the validateTorrents field.
|
||||
func (r *mutationResolver) ValidateTorrents(ctx context.Context, 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// TorrentDaemon is the resolver for the torrentDaemon field.
|
||||
func (r *mutationResolver) TorrentDaemon(ctx context.Context) (*model.TorrentDaemonMutation, error) {
|
||||
return &model.TorrentDaemonMutation{}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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 (
|
||||
"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 *queryResolver) Torrents(ctx context.Context, 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.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
|
||||
// TorrentDaemon is the resolver for the torrentDaemon field.
|
||||
func (r *queryResolver) TorrentDaemon(ctx context.Context) (*model.TorrentDaemonQuery, error) {
|
||||
return &model.TorrentDaemonQuery{}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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 (
|
||||
"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
|
||||
// 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 (
|
||||
"context"
|
||||
|
||||
graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql"
|
||||
"git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model"
|
||||
"github.com/anacrolix/torrent/types"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (r *torrentResolver) Files(ctx context.Context, obj *model.Torrent) ([]*model.TorrentFile, error) {
|
||||
out := []*model.TorrentFile{}
|
|
@ -18,13 +18,19 @@ import (
|
|||
"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 {
|
||||
graphqlHandler := handler.NewDefaultServer(
|
||||
graph.NewExecutableSchema(
|
||||
graph.Config{
|
||||
Resolvers: &resolver.Resolver{Service: service, VFS: vfs},
|
||||
Directives: graph.DirectiveRoot{
|
||||
OneOf: graph.OneOf,
|
||||
OneOf: graph.OneOf,
|
||||
Resolver: noopDirective,
|
||||
Stream: noopDirective,
|
||||
},
|
||||
},
|
||||
),
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
||||
|
@ -11,14 +11,14 @@ import (
|
|||
|
||||
func BadgerLogger(name ...string) badger.Logger {
|
||||
return &badgerLogger{
|
||||
L: rlog.Component(append(name, "badger")...).Slog(),
|
||||
L: rlog.Component(append([]string{"badger"}, name...)...).Nested(2),
|
||||
}
|
||||
}
|
||||
|
||||
var _ badger.Logger = (*badgerLogger)(nil)
|
||||
|
||||
type badgerLogger struct {
|
||||
L *slog.Logger
|
||||
L *rlog.Logger
|
||||
}
|
||||
|
||||
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{}) {
|
||||
l.L.Error(fmtBadgerLog(m, f...))
|
||||
ctx := context.Background()
|
||||
l.L.Error(ctx, fmtBadgerLog(m, f...))
|
||||
}
|
||||
|
||||
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{}) {
|
||||
l.L.Info(fmtBadgerLog(m, f...))
|
||||
ctx := context.Background()
|
||||
l.L.Info(ctx, fmtBadgerLog(m, f...))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
us := make([]U, len(ts))
|
||||
for i := range ts {
|
||||
|
@ -166,18 +181,79 @@ func (s *Controller) ValidateTorrent(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) SetFilePriority(ctx context.Context, file *torrent.File, priority types.PiecePriority) error {
|
||||
log := c.log.With(slog.String("file", file.Path()), slog.Int("priority", int(priority)))
|
||||
log.Info(ctx, "set pritority for file")
|
||||
func (c *Controller) SetPriority(ctx context.Context, filePath *string, priority types.PiecePriority) error {
|
||||
log := c.log.With(slog.Int("priority", int(priority)))
|
||||
|
||||
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) {
|
||||
v.Priority = priority
|
||||
return v, nil
|
||||
})
|
||||
if err != nil {
|
||||
if err == kv.ErrKeyNotFound {
|
||||
err := c.fileProperties.Set(ctx, file.Path(), FileProperties{Priority: priority})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
file.SetPriority(priority)
|
||||
return nil
|
||||
}
|
||||
|
@ -204,7 +280,6 @@ func (c *Controller) initializeTorrentPriories(ctx context.Context) error {
|
|||
}
|
||||
|
||||
file.SetPriority(props.Priority)
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const delimeter rune = 0x1F
|
||||
|
||||
func pkToBytes(pk metainfo.PieceKey) []byte {
|
||||
key := make([]byte, len(pk.InfoHash.Bytes()))
|
||||
copy(key, pk.InfoHash.Bytes())
|
||||
binary.BigEndian.AppendUint32(key, uint32(pk.Index))
|
||||
key := make([]byte, 0, len(pk.InfoHash.Bytes())+1+4)
|
||||
key = append(key, pk.InfoHash.Bytes()...)
|
||||
key = append(key, byte(delimeter))
|
||||
key = binary.BigEndian.AppendUint32(key, uint32(pk.Index))
|
||||
return key
|
||||
}
|
||||
|
||||
func (k *badgerPieceCompletion) Get(pk metainfo.PieceKey) (storage.Completion, error) {
|
||||
completion := storage.Completion{
|
||||
Ok: true,
|
||||
Complete: false,
|
||||
Ok: false,
|
||||
}
|
||||
err := k.db.View(func(tx *badger.Txn) error {
|
||||
item, err := tx.Get(pkToBytes(pk))
|
||||
if err != nil {
|
||||
if err == badger.ErrKeyNotFound {
|
||||
completion.Complete = false
|
||||
completion.Ok = false
|
||||
return nil
|
||||
}
|
||||
|
@ -71,11 +76,12 @@ func (k *badgerPieceCompletion) Get(pk metainfo.PieceKey) (storage.Completion, e
|
|||
}
|
||||
compl := PieceCompletionState(valCopy[0])
|
||||
|
||||
completion.Ok = true
|
||||
switch compl {
|
||||
case PieceComplete:
|
||||
completion.Ok = true
|
||||
completion.Complete = true
|
||||
case PieceNotComplete:
|
||||
completion.Ok = true
|
||||
completion.Complete = false
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,12 @@ func setupStorage(cfg config.TorrentClient) (*fileStorage, storage.PieceCompleti
|
|||
if err := os.MkdirAll(pcp, 0744); err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating servers piece completion: %w", err)
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -22,34 +21,38 @@ import (
|
|||
// OpenTorrent implements storage.ClientImplCloser.
|
||||
func (me *fileStorage) OpenTorrent(info *metainfo.Info, infoHash infohash.T) (storage.TorrentImpl, error) {
|
||||
ctx := context.Background()
|
||||
log := me.log
|
||||
log := me.log.With(slog.String("infohash", infoHash.HexString()))
|
||||
|
||||
dir := torrentDir(me.baseDir, infoHash)
|
||||
legacyDir := filepath.Join(me.baseDir, info.Name)
|
||||
// dir := torrentDir(me.baseDir, infoHash)
|
||||
// legacyDir := filepath.Join(me.baseDir, info.Name)
|
||||
|
||||
log = log.With(slog.String("legacy_dir", legacyDir), slog.String("dir", dir))
|
||||
if _, err := os.Stat(legacyDir); err == nil {
|
||||
log.Warn(ctx, "legacy torrent dir found, renaming", slog.String("dir", dir))
|
||||
err = os.Rename(legacyDir, dir)
|
||||
if err != nil {
|
||||
return storage.TorrentImpl{}, fmt.Errorf("error renaming legacy torrent dir: %w", err)
|
||||
}
|
||||
// log = log.With(slog.String("legacy_dir", legacyDir), slog.String("dir", dir))
|
||||
// if _, err := os.Stat(legacyDir); err == nil {
|
||||
// log.Warn(ctx, "legacy torrent dir found, renaming", slog.String("dir", 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 {
|
||||
log.Error(ctx, "error opening torrent", rlog.Error(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)
|
||||
return impl, err
|
||||
}
|
||||
|
||||
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 @resolver on INPUT_FIELD_DEFINITION | FIELD_DEFINITION
|
||||
directive @stream on FIELD_DEFINITION
|
||||
type ArchiveFS implements Dir & FsEntry {
|
||||
name: String!
|
||||
entries: [FsEntry!]!
|
||||
entries: [FsEntry!]! @resolver
|
||||
size: Int!
|
||||
}
|
||||
input BooleanFilter @oneOf {
|
||||
|
@ -43,9 +44,7 @@ input IntFilter @oneOf {
|
|||
in: [Int!]
|
||||
}
|
||||
type Mutation {
|
||||
validateTorrents(filter: TorrentFilter!): Boolean!
|
||||
cleanupTorrents(files: Boolean, dryRun: Boolean!): CleanupResponse!
|
||||
downloadTorrent(infohash: String!, file: String): DownloadTorrentResponse
|
||||
torrentDaemon: TorrentDaemonMutation @resolver
|
||||
uploadFile(dir: String!, file: Upload!): Boolean!
|
||||
dedupeStorage: Int!
|
||||
}
|
||||
|
@ -58,12 +57,12 @@ interface Progress {
|
|||
total: Int!
|
||||
}
|
||||
type Query {
|
||||
torrents(filter: TorrentsFilter): [Torrent!]!
|
||||
torrentDaemon: TorrentDaemonQuery @resolver
|
||||
fsEntry(path: String!): FsEntry
|
||||
}
|
||||
type ResolverFS implements Dir & FsEntry {
|
||||
name: String!
|
||||
entries: [FsEntry!]!
|
||||
entries: [FsEntry!]! @resolver
|
||||
}
|
||||
type Schema {
|
||||
query: Query
|
||||
|
@ -71,7 +70,7 @@ type Schema {
|
|||
}
|
||||
type SimpleDir implements Dir & FsEntry {
|
||||
name: String!
|
||||
entries: [FsEntry!]!
|
||||
entries: [FsEntry!]! @resolver
|
||||
}
|
||||
type SimpleFile implements File & FsEntry {
|
||||
name: String!
|
||||
|
@ -90,20 +89,28 @@ type Task {
|
|||
id: ID!
|
||||
}
|
||||
type Torrent {
|
||||
name: String!
|
||||
name: String! @resolver
|
||||
infohash: String!
|
||||
bytesCompleted: Int!
|
||||
torrentFilePath: String!
|
||||
bytesMissing: Int!
|
||||
files: [TorrentFile!]!
|
||||
excludedFiles: [TorrentFile!]!
|
||||
peers: [TorrentPeer!]!
|
||||
downloading: Boolean!
|
||||
priority: TorrentPriority! @resolver
|
||||
files: [TorrentFile!]! @resolver
|
||||
excludedFiles: [TorrentFile!]! @resolver
|
||||
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 {
|
||||
name: String!
|
||||
torrent: Torrent!
|
||||
entries: [FsEntry!]!
|
||||
entries: [FsEntry!]! @resolver
|
||||
}
|
||||
type TorrentFile {
|
||||
filename: String!
|
||||
|
@ -126,6 +133,22 @@ type TorrentPeer {
|
|||
port: Int!
|
||||
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 {
|
||||
torrent: Torrent!
|
||||
current: Int!
|
||||
|
@ -137,6 +160,6 @@ input TorrentsFilter {
|
|||
bytesCompleted: IntFilter
|
||||
bytesMissing: IntFilter
|
||||
peersCount: IntFilter
|
||||
downloading: BooleanFilter
|
||||
priority: TorrentPriorityFilter
|
||||
}
|
||||
scalar Upload
|
||||
|
|
Loading…
Reference in a new issue