Compare commits

...

2 commits

Author SHA1 Message Date
199a82ff0c torrent priority and piece state fix
All checks were successful
docker / build-docker (linux/amd64) (push) Successful in 2m16s
docker / build-docker (linux/arm64) (push) Successful in 2m14s
2024-07-07 23:09:13 +03:00
13ce2aa07f faster docker builds 2024-06-28 16:08:30 +03:00
34 changed files with 2231 additions and 961 deletions

View file

@ -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
View file

@ -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",

View file

@ -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

View file

@ -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/*

View file

@ -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
View file

@ -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
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=
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=

View file

@ -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!
}

View file

@ -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
}

View file

@ -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

View 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
}

View 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!
}

View file

@ -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
}

View 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
}

View file

@ -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 {

View file

@ -35,23 +35,34 @@ func AddHandler(nh slog.Handler) {
type Logger struct {
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)
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

View file

@ -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
}

View file

@ -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
}

View file

@ -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"`
@ -254,5 +274,5 @@ type TorrentsFilter struct {
BytesCompleted *IntFilter `json:"bytesCompleted,omitempty"`
BytesMissing *IntFilter `json:"bytesMissing,omitempty"`
PeersCount *IntFilter `json:"peersCount,omitempty"`
Downloading *BooleanFilter `json:"downloading,omitempty"`
Priority *TorrentPriorityFilter `json:"priority,omitempty"`
}

View file

@ -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"

View file

@ -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.

View file

@ -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.

View file

@ -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"

View 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 }

View 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 }

View file

@ -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{}

View file

@ -18,6 +18,10 @@ 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(
@ -25,6 +29,8 @@ func GraphQLHandler(service *torrent.Daemon, vfs vfs.Filesystem) http.Handler {
Resolvers: &resolver.Resolver{Service: service, VFS: vfs},
Directives: graph.DirectiveRoot{
OneOf: graph.OneOf,
Resolver: noopDirective,
Stream: noopDirective,
},
},
),

View file

@ -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...))
}

View file

@ -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()))

View file

@ -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
}

View file

@ -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)

View file

@ -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)
// 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 {
return storage.TorrentImpl{}, fmt.Errorf("error renaming legacy torrent dir: %w", err)
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 {

View file

@ -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