Compare commits

..

No commits in common. "199a82ff0c4206bf724dbe273bd3aaf99bdc1a07" and "0fa3a914470b0aeb3d6000e466243126682d0733" have entirely different histories.

34 changed files with 961 additions and 2231 deletions

View file

@ -22,9 +22,18 @@ 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/sources/torrent.Controller" type: "*git.kmsign.ru/royalcat/tstor/src/host/torrent.Controller"
TorrentFile: TorrentFile:
extraFields: extraFields:
F: F:
@ -34,37 +43,32 @@ 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/sources/torrent.TorrentFS" type: "*git.kmsign.ru/royalcat/tstor/src/host/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
View file

@ -5,14 +5,14 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Generate GraphQL", "name": "Launch file",
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "debug", "mode": "debug",
"program": "${workspaceFolder}/cmd/generate-graphql/main.go", "program": "${file}"
}, },
{ {
"name": "TStor", "name": "Launch Package",
"type": "go", "type": "go",
"request": "launch", "request": "launch",
"mode": "auto", "mode": "auto",

View file

@ -1,4 +1,4 @@
FROM --platform=$BUILDPLATFORM golang:1.22 as builder FROM golang:1.22 as builder
WORKDIR /app WORKDIR /app
@ -14,9 +14,7 @@ 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

View file

@ -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 cmd/generate-graphql/* $(shell find graphql -type f) src/delivery/graphql/generated.go: .gqlgen.yml graphql/* graphql/types/* cmd/generate-graphql/*
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/*

View file

@ -10,23 +10,6 @@ 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 {
} }
@ -42,6 +25,9 @@ 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 {
@ -62,33 +48,18 @@ func (fieldDirectiveFix) GenerateCode(cfg *codegen.Data) error {
return nil return nil
} }
type resolverDirective struct { func main() {
cfg, err := config.LoadConfigFromDefaultLocations()
if err != nil {
fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
os.Exit(2)
} }
func (resolverDirective) Name() string { err = api.Generate(cfg,
return "Resolver directive support" api.AddPlugin(&fieldDirectiveFix{}),
} )
if err != nil {
func (resolverDirective) GenerateCode(cfg *codegen.Data) error { fmt.Fprintln(os.Stderr, err.Error())
const directiveName = "resolver" os.Exit(3)
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
View file

@ -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.49 github.com/99designs/gqlgen v0.17.45
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.1 github.com/anacrolix/torrent v1.56.0
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.16 github.com/vektah/gqlparser/v2 v2.5.11
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.26.0 golang.org/x/net v0.25.0
golang.org/x/sync v0.7.0 golang.org/x/sync v0.7.0
golang.org/x/sys v0.21.0 golang.org/x/sys v0.20.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.1 // indirect github.com/sosodev/duration v1.3.0 // 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.24.0 // indirect golang.org/x/crypto v0.23.0 // indirect
golang.org/x/mod v0.18.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/tools v0.21.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
View file

@ -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.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ= github.com/99designs/gqlgen v0.17.45 h1:bH0AH67vIJo8JKNKPJP+pOPpQhZeuVRQLf53dKIpDik=
github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0= github.com/99designs/gqlgen v0.17.45/go.mod h1:Bas0XQ+Jiu/Xm5E33jC8sES3G+iC2esHBMXcq0fUPs0=
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.1 h1:QeJMOP0NuhpQ5dATsOqEL0vUO85aPMNMGP2FACNt0Eg= github.com/anacrolix/torrent v1.56.0 h1:g/sM0K/BaWUv4Htu2bblLBhIxGdFZ1MUCoD7lcvemlo=
github.com/anacrolix/torrent v1.56.1/go.mod h1:5DMHbeIM1TuC5wTQ99XieKKLiYZYz6iB2lyZpKZEr6w= github.com/anacrolix/torrent v1.56.0/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.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= github.com/sosodev/duration v1.3.0 h1:g3E6mto+hFdA2uZXeNDYff8LYeg7v5D4YKP/Ng/NUkE=
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/sosodev/duration v1.3.0/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.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8= github.com/vektah/gqlparser/v2 v2.5.11 h1:JJxLtXIoN7+3x6MBdtIP59TP1RANnY7pXOaDnADQSf8=
github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww= github.com/vektah/gqlparser/v2 v2.5.11/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc=
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.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
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.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.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.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
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.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.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.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
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=

View file

@ -1,10 +1,26 @@
type Mutation { type Mutation {
torrentDaemon: TorrentDaemonMutation @resolver validateTorrents(filter: TorrentFilter!): Boolean!
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!
} }

View file

@ -1,5 +1,46 @@
type Query { type Query {
torrentDaemon: TorrentDaemonQuery @resolver torrents(filter: TorrentsFilter): [Torrent!]!
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
}

View file

@ -1,6 +1,4 @@
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

View file

@ -1,18 +0,0 @@
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
}

View file

@ -1,27 +0,0 @@
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!
}

View file

@ -1,31 +0,0 @@
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
}

View file

@ -14,7 +14,7 @@ interface File implements FsEntry {
type SimpleDir implements Dir & FsEntry { type SimpleDir implements Dir & FsEntry {
name: String! name: String!
entries: [FsEntry!]! @resolver entries: [FsEntry!]!
} }
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!]! @resolver entries: [FsEntry!]!
} }
type ArchiveFS implements Dir & FsEntry { type ArchiveFS implements Dir & FsEntry {
name: String! name: String!
entries: [FsEntry!]! @resolver entries: [FsEntry!]!
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!]! @resolver entries: [FsEntry!]!
} }
type TorrentFileEntry implements File & FsEntry { type TorrentFileEntry implements File & FsEntry {

View file

@ -1,13 +1,15 @@
type Torrent { type Torrent {
name: String! @resolver name: String!
infohash: String! infohash: String!
bytesCompleted: Int! bytesCompleted: Int!
torrentFilePath: String! torrentFilePath: String!
bytesMissing: Int! bytesMissing: Int!
priority: TorrentPriority! @resolver files: [TorrentFile!]!
files: [TorrentFile!]! @resolver excludedFiles: [TorrentFile!]!
excludedFiles: [TorrentFile!]! @resolver peers: [TorrentPeer!]!
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 {
@ -23,11 +25,3 @@ type TorrentPeer {
port: Int! port: Int!
clientName: String! clientName: String!
} }
enum TorrentPriority {
NONE
NORMAL
HIGH
READAHEAD
NOW
}

View file

@ -35,34 +35,23 @@ 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+l.callNesting, pcs[:]) runtime.Callers(3, 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)
} }
@ -82,10 +71,16 @@ 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, handler: log.handler.WithAttrs([]slog.Attr{
component: append(log.component, name), slog.String(componentKey, strings.Join(c, componentSep)),
}),
component: c,
} }
} }
@ -96,20 +91,23 @@ 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

View file

@ -3,8 +3,6 @@ package model
import ( import (
"slices" "slices"
"strings" "strings"
"github.com/anacrolix/torrent/types"
) )
type Filter[T any] interface { type Filter[T any] interface {
@ -54,23 +52,3 @@ 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
}

View file

@ -29,11 +29,24 @@ 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
} }

View file

@ -8,7 +8,6 @@ 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 {
@ -177,23 +176,13 @@ 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"`
@ -249,15 +238,6 @@ 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"`
@ -274,5 +254,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"`
Priority *TorrentPriorityFilter `json:"priority,omitempty"` Downloading *BooleanFilter `json:"downloading,omitempty"`
} }

View file

@ -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.49 // Code generated by github.com/99designs/gqlgen version v0.17.45
import ( import (
"context" "context"

View file

@ -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.49 // Code generated by github.com/99designs/gqlgen version v0.17.45
import ( import (
"context" "context"
@ -11,14 +11,84 @@ 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"
) )
// TorrentDaemon is the resolver for the torrentDaemon field. // ValidateTorrents is the resolver for the validateTorrents field.
func (r *mutationResolver) TorrentDaemon(ctx context.Context) (*model.TorrentDaemonMutation, error) { func (r *mutationResolver) ValidateTorrents(ctx context.Context, filter model.TorrentFilter) (bool, error) {
return &model.TorrentDaemonMutation{}, nil 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
} }
// UploadFile is the resolver for the uploadFile field. // UploadFile is the resolver for the uploadFile field.

View file

@ -2,18 +2,89 @@ 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.49 // Code generated by github.com/99designs/gqlgen version v0.17.45
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"
) )
// TorrentDaemon is the resolver for the torrentDaemon field. // Torrents is the resolver for the torrents field.
func (r *queryResolver) TorrentDaemon(ctx context.Context) (*model.TorrentDaemonQuery, error) { func (r *queryResolver) Torrents(ctx context.Context, filter *model.TorrentsFilter) ([]*model.Torrent, error) {
return &model.TorrentDaemonQuery{}, nil 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
} }
// FsEntry is the resolver for the fsEntry field. // FsEntry is the resolver for the fsEntry field.

View file

@ -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.49 // Code generated by github.com/99designs/gqlgen version v0.17.45
import ( import (
"context" "context"

View file

@ -2,14 +2,13 @@ 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.49 // Code generated by github.com/99designs/gqlgen version v0.17.45
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.
@ -17,11 +16,6 @@ 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{}

View file

@ -1,90 +0,0 @@
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 }

View file

@ -1,94 +0,0 @@
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 }

View file

@ -18,10 +18,6 @@ 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(
@ -29,8 +25,6 @@ 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,
}, },
}, },
), ),

View file

@ -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([]string{"badger"}, name...)...).Nested(2), L: rlog.Component(append(name, "badger")...).Slog(),
} }
} }
var _ badger.Logger = (*badgerLogger)(nil) var _ badger.Logger = (*badgerLogger)(nil)
type badgerLogger struct { type badgerLogger struct {
L *rlog.Logger L *slog.Logger
} }
func fmtBadgerLog(m string, f ...any) string { func fmtBadgerLog(m string, f ...any) string {
@ -26,21 +26,17 @@ func fmtBadgerLog(m string, f ...any) string {
} }
func (l *badgerLogger) Errorf(m string, f ...interface{}) { func (l *badgerLogger) Errorf(m string, f ...interface{}) {
ctx := context.Background() l.L.Error(fmtBadgerLog(m, f...))
l.L.Error(ctx, fmtBadgerLog(m, f...))
} }
func (l *badgerLogger) Warningf(m string, f ...interface{}) { func (l *badgerLogger) Warningf(m string, f ...interface{}) {
ctx := context.Background() l.L.Warn(fmtBadgerLog(m, f...))
l.L.Warn(ctx, fmtBadgerLog(m, f...))
} }
func (l *badgerLogger) Infof(m string, f ...interface{}) { func (l *badgerLogger) Infof(m string, f ...interface{}) {
ctx := context.Background() l.L.Info(fmtBadgerLog(m, f...))
l.L.Info(ctx, fmtBadgerLog(m, f...))
} }
func (l *badgerLogger) Debugf(m string, f ...interface{}) { func (l *badgerLogger) Debugf(m string, f ...interface{}) {
ctx := context.Background() l.L.Debug(fmtBadgerLog(m, f...))
l.L.Debug(ctx, fmtBadgerLog(m, f...))
} }

View file

@ -111,21 +111,6 @@ 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 {
@ -181,79 +166,18 @@ func (s *Controller) ValidateTorrent(ctx context.Context) error {
return nil return nil
} }
func (c *Controller) SetPriority(ctx context.Context, filePath *string, priority types.PiecePriority) error { func (c *Controller) SetFilePriority(ctx context.Context, file *torrent.File, priority types.PiecePriority) error {
log := c.log.With(slog.Int("priority", int(priority))) log := c.log.With(slog.String("file", file.Path()), 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
} }
@ -280,6 +204,7 @@ 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()))

View file

@ -43,26 +43,21 @@ 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, 0, len(pk.InfoHash.Bytes())+1+4) key := make([]byte, len(pk.InfoHash.Bytes()))
key = append(key, pk.InfoHash.Bytes()...) copy(key, pk.InfoHash.Bytes())
key = append(key, byte(delimeter)) binary.BigEndian.AppendUint32(key, uint32(pk.Index))
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{
Complete: false, Ok: true,
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
} }
@ -76,12 +71,11 @@ 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
} }

View file

@ -14,12 +14,6 @@ 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)

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io/fs"
"log/slog" "log/slog"
"os" "os"
"path/filepath" "path/filepath"
@ -21,38 +22,34 @@ 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.With(slog.String("infohash", infoHash.HexString())) log := me.log
// 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 {
log.Error(ctx, "error opening torrent", rlog.Error(err)) return storage.TorrentImpl{}, fmt.Errorf("error renaming legacy torrent dir: %w", 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 {

View file

@ -1,9 +1,8 @@
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!]! @resolver entries: [FsEntry!]!
size: Int! size: Int!
} }
input BooleanFilter @oneOf { input BooleanFilter @oneOf {
@ -44,7 +43,9 @@ input IntFilter @oneOf {
in: [Int!] in: [Int!]
} }
type Mutation { type Mutation {
torrentDaemon: TorrentDaemonMutation @resolver validateTorrents(filter: TorrentFilter!): Boolean!
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!
} }
@ -57,12 +58,12 @@ interface Progress {
total: Int! total: Int!
} }
type Query { type Query {
torrentDaemon: TorrentDaemonQuery @resolver torrents(filter: TorrentsFilter): [Torrent!]!
fsEntry(path: String!): FsEntry fsEntry(path: String!): FsEntry
} }
type ResolverFS implements Dir & FsEntry { type ResolverFS implements Dir & FsEntry {
name: String! name: String!
entries: [FsEntry!]! @resolver entries: [FsEntry!]!
} }
type Schema { type Schema {
query: Query query: Query
@ -70,7 +71,7 @@ type Schema {
} }
type SimpleDir implements Dir & FsEntry { type SimpleDir implements Dir & FsEntry {
name: String! name: String!
entries: [FsEntry!]! @resolver entries: [FsEntry!]!
} }
type SimpleFile implements File & FsEntry { type SimpleFile implements File & FsEntry {
name: String! name: String!
@ -89,28 +90,20 @@ type Task {
id: ID! id: ID!
} }
type Torrent { type Torrent {
name: String! @resolver name: String!
infohash: String! infohash: String!
bytesCompleted: Int! bytesCompleted: Int!
torrentFilePath: String! torrentFilePath: String!
bytesMissing: Int! bytesMissing: Int!
priority: TorrentPriority! @resolver files: [TorrentFile!]!
files: [TorrentFile!]! @resolver excludedFiles: [TorrentFile!]!
excludedFiles: [TorrentFile!]! @resolver peers: [TorrentPeer!]!
peers: [TorrentPeer!]! @resolver downloading: Boolean!
}
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!]! @resolver entries: [FsEntry!]!
} }
type TorrentFile { type TorrentFile {
filename: String! filename: String!
@ -133,22 +126,6 @@ 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!
@ -160,6 +137,6 @@ input TorrentsFilter {
bytesCompleted: IntFilter bytesCompleted: IntFilter
bytesMissing: IntFilter bytesMissing: IntFilter
peersCount: IntFilter peersCount: IntFilter
priority: TorrentPriorityFilter downloading: BooleanFilter
} }
scalar Upload scalar Upload