diff --git a/cmd/tstor/main.go b/cmd/tstor/main.go index 3b4dce0..94979f5 100644 --- a/cmd/tstor/main.go +++ b/cmd/tstor/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "log/slog" @@ -14,20 +15,21 @@ import ( "time" "git.kmsign.ru/royalcat/tstor/src/config" + "git.kmsign.ru/royalcat/tstor/src/delivery" "git.kmsign.ru/royalcat/tstor/src/host" "git.kmsign.ru/royalcat/tstor/src/host/datastorage" "git.kmsign.ru/royalcat/tstor/src/host/service" "git.kmsign.ru/royalcat/tstor/src/host/store" "git.kmsign.ru/royalcat/tstor/src/host/vfs" + "git.kmsign.ru/royalcat/tstor/src/telemetry" "github.com/urfave/cli/v2" wnfs "github.com/willscott/go-nfs" + _ "git.kmsign.ru/royalcat/tstor/pkg/rlog" "git.kmsign.ru/royalcat/tstor/src/export/fuse" "git.kmsign.ru/royalcat/tstor/src/export/httpfs" "git.kmsign.ru/royalcat/tstor/src/export/nfs" "git.kmsign.ru/royalcat/tstor/src/export/webdav" - "git.kmsign.ru/royalcat/tstor/src/http" - dlog "git.kmsign.ru/royalcat/tstor/src/log" ) const ( @@ -65,7 +67,17 @@ func run(configPath string) error { if err != nil { return fmt.Errorf("error loading configuration: %w", err) } - dlog.Load(&conf.Log) + // dlog.Load(&conf.Log) + + if conf.OtelHttp != "" { + ctx := context.Background() + client, err := telemetry.Setup(ctx, conf.OtelHttp) + if err != nil { + return err + } + + defer client.Shutdown(ctx) + } log := slog.Default().With("component", "run") @@ -113,7 +125,10 @@ func run(configPath string) error { c.AddDhtNodes(conf.TorrentClient.DHTNodes) defer c.Close() - ts := service.NewService(conf.SourceDir, c, st, excludedFilesStore, infoBytesStore, conf.TorrentClient.AddTimeout, conf.TorrentClient.ReadTimeout) + ts, err := service.NewService(conf.SourceDir, conf.TorrentClient, c, st, excludedFilesStore, infoBytesStore, conf.TorrentClient.AddTimeout, conf.TorrentClient.ReadTimeout) + if err != nil { + return fmt.Errorf("error creating service: %w", err) + } if err := os.MkdirAll(conf.SourceDir, 0744); err != nil { return fmt.Errorf("error creating data folder: %w", err) @@ -226,9 +241,9 @@ func run(configPath string) error { }() go func() { - logFilename := filepath.Join(conf.Log.Path, dlog.FileName) + logFilename := filepath.Join(conf.Log.Path, "logs") - err := http.New(nil, service.NewStats(), ts, logFilename, conf) + err := delivery.New(nil, service.NewStats(), ts, sfs, logFilename, conf) if err != nil { log.Error("error initializing HTTP server", "error", err) } diff --git a/go.mod b/go.mod index 047396f..4d9c47f 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,9 @@ go 1.22.1 require ( github.com/99designs/gqlgen v0.17.43 github.com/RoaringBitmap/roaring v1.2.3 - github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 + github.com/agoda-com/opentelemetry-go/otelslog v0.1.1 + github.com/agoda-com/opentelemetry-logs-go v0.3.0 + github.com/anacrolix/dht/v2 v2.21.1 github.com/anacrolix/log v0.14.6-0.20231202035202-ed7a02cad0b4 github.com/anacrolix/missinggo/v2 v2.7.3 github.com/anacrolix/torrent v1.55.0 @@ -25,14 +27,22 @@ require ( github.com/knadh/koanf/providers/file v0.1.0 github.com/knadh/koanf/providers/structs v0.1.0 github.com/knadh/koanf/v2 v2.0.1 - github.com/lmittmann/tint v1.0.4 github.com/nwaples/rardecode/v2 v2.0.0-beta.2 - github.com/royalcat/kv v0.0.0-20240316134516-1bb692abce73 + github.com/ravilushqa/otelgqlgen v0.15.0 + github.com/royalcat/kv v0.0.0-20240318203654-181645f85b10 + github.com/rs/zerolog v1.32.0 + github.com/samber/slog-multi v1.0.2 + github.com/samber/slog-zerolog v1.0.0 github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c github.com/stretchr/testify v1.8.4 github.com/urfave/cli/v2 v2.27.0 github.com/vektah/gqlparser/v2 v2.5.11 github.com/willscott/go-nfs v0.0.2 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 + go.opentelemetry.io/otel/exporters/prometheus v0.46.0 + go.opentelemetry.io/otel/sdk v1.24.0 + go.opentelemetry.io/otel/sdk/metric v1.24.0 go.uber.org/multierr v1.11.0 golang.org/x/exp v0.0.0-20231226003508-02704c960a9b golang.org/x/net v0.19.0 @@ -57,11 +67,13 @@ require ( github.com/andybalholm/brotli v1.0.6 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/benbjohnson/immutable v0.4.1-0.20221220213129-8932b999621d // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.2.2 // indirect github.com/bodgit/plumbing v1.3.0 // indirect github.com/bodgit/windows v1.0.1 // indirect github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect github.com/bytedance/sonic v1.9.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect @@ -75,20 +87,21 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-llsqlite/adapter v0.0.0-20230927005056-7f5ce7f0c916 // indirect github.com/go-llsqlite/crawshaw v0.4.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/glog v1.1.0 // indirect + github.com/golang/glog v1.1.2 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/flatbuffers v2.0.8+incompatible // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -96,7 +109,9 @@ require ( github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -125,12 +140,17 @@ require ( github.com/pion/webrtc/v3 v3.1.42 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.18.0 // indirect + github.com/prometheus/client_model v0.6.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect - github.com/sosodev/duration v1.1.0 // indirect + github.com/samber/lo v1.38.1 // indirect + github.com/sosodev/duration v1.2.0 // indirect github.com/tidwall/btree v1.6.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect @@ -139,18 +159,24 @@ require ( github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel v1.8.0 // indirect - go.opentelemetry.io/otel/trace v1.8.0 // indirect + go.opentelemetry.io/contrib v1.21.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect go4.org v0.0.0-20200411211856-f5505b9728dd // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/mod v0.14.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.16.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect + google.golang.org/grpc v1.61.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.22.3 // indirect modernc.org/mathutil v1.5.0 // indirect diff --git a/go.sum b/go.sum index 29df124..f56ac68 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,10 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/agoda-com/opentelemetry-go/otelslog v0.1.1 h1:6nV8PZCzySHuh9kP/HZ2OJqGucwQiM+yZRugKDvtzj4= +github.com/agoda-com/opentelemetry-go/otelslog v0.1.1/go.mod h1:CSc0veIcY/HsIfH7l5PGtIpRvBttk09QUQlweVkD2PI= +github.com/agoda-com/opentelemetry-logs-go v0.3.0 h1:d2lMVUfCDeLzVgTxMeSU8IWaMXjwD4sVKigEZBGwcsw= +github.com/agoda-com/opentelemetry-logs-go v0.3.0/go.mod h1:k3QR1O5AOl+dFC7pkrK9wWmoD72jjDONPFHi9dAgLJc= github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 h1:byYvvbfSo3+9efR4IeReh77gVs4PnNDR3AMOE9NJ7a0= github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0/go.mod h1:q37NoqncT41qKc048STsifIt69LfUJ8SrWWcz/yam5k= github.com/alecthomas/assert/v2 v2.0.0-alpha3 h1:pcHeMvQ3OMstAWgaeaXIAL8uzB9xMm2zlxt+/4ml8lk= @@ -48,8 +52,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= -github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444 h1:8V0K09lrGoeT2KRJNOtspA7q+OMxGwQqK/Ug0IiaaRE= -github.com/anacrolix/dht/v2 v2.19.2-0.20221121215055-066ad8494444/go.mod h1:MctKM1HS5YYDb3F30NGJxLE+QPuqWoT5ReW/4jt8xew= +github.com/anacrolix/dht/v2 v2.21.1 h1:s1rKkfLLcmBHKv4v/mtMkIeHIEptzEFiB6xVu54+5/o= +github.com/anacrolix/dht/v2 v2.21.1/go.mod h1:SDGC+sEs1pnO2sJGYuhvIis7T8749dDHNfcjtdH4e3g= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= @@ -115,6 +119,7 @@ github.com/benbjohnson/immutable v0.4.1-0.20221220213129-8932b999621d h1:2qVb9bs github.com/benbjohnson/immutable v0.4.1-0.20221220213129-8932b999621d/go.mod h1:iAr8OjJGLnLmVUr9MZ/rz4PWUy6Ouc2JLYuMArmvAJM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/billziss-gh/cgofuse v1.5.0 h1:kH516I/s+Ab4diL/Y/ayFeUjjA8ey+JK12xDfBf4HEs= github.com/billziss-gh/cgofuse v1.5.0/go.mod h1:LJjoaUojlVjgo5GQoEJTcJNqZJeRU0nCR84CyxKt2YM= @@ -134,6 +139,8 @@ github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -148,6 +155,7 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -212,8 +220,8 @@ github.com/go-llsqlite/crawshaw v0.4.0/go.mod h1:/YJdV7uBQaYDE0fwe4z3wwJIZBJxdYz github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -233,6 +241,7 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg78 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M= github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -240,8 +249,8 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= +github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= +github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= @@ -284,8 +293,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -306,6 +315,8 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -367,12 +378,16 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= -github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -465,21 +480,31 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= +github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 h1:UVArwN/wkKjMVhh2EQGC0tEc1+FqiLlvYXY5mQ2f8Wg= github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93/go.mod h1:Nfe4efndBz4TibWycNE+lqyJZiMX4ycx+QKV8Ta0f/o= +github.com/ravilushqa/otelgqlgen v0.15.0 h1:U85nrlweMXTGaMChUViYM39/MXBZVeVVlpuHq+6eECQ= +github.com/ravilushqa/otelgqlgen v0.15.0/go.mod h1:o+1Eju0VySmgq2BP8Vupz2YrN21Bj7D7imBqu3m2uB8= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -489,17 +514,24 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/royalcat/kv v0.0.0-20240316120131-b774a9bff6f7 h1:fmBTD0RaTvWbd6KrgLVXSDUJ4dWTPOFUdkdHp+kYvRM= -github.com/royalcat/kv v0.0.0-20240316120131-b774a9bff6f7/go.mod h1:Ff0Z/r1H3ojacpEe8SashMKJx6YCIhWrYtpdV8Y/k3A= -github.com/royalcat/kv v0.0.0-20240316134516-1bb692abce73 h1:zeFE8Nx11oD6In+f+VDYwGH72t7NV6L5dqaNbDIhB1E= -github.com/royalcat/kv v0.0.0-20240316134516-1bb692abce73/go.mod h1:Ff0Z/r1H3ojacpEe8SashMKJx6YCIhWrYtpdV8Y/k3A= +github.com/royalcat/kv v0.0.0-20240318203654-181645f85b10 h1:8vwpCzvVqzNzkYRH9kA3GV5fkWs+8s0jdxtGvswL/MU= +github.com/royalcat/kv v0.0.0-20240318203654-181645f85b10/go.mod h1:Ff0Z/r1H3ojacpEe8SashMKJx6YCIhWrYtpdV8Y/k3A= github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 h1:Lt9DzQALzHoDwMBGJ6v8ObDPR0dzr2a6sXTB1Fq7IHs= github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +github.com/samber/slog-multi v1.0.2 h1:6BVH9uHGAsiGkbbtQgAOQJMpKgV8unMrHhhJaw+X1EQ= +github.com/samber/slog-multi v1.0.2/go.mod h1:uLAvHpGqbYgX4FSL0p1ZwoLuveIAJvBECtE07XmYvFo= +github.com/samber/slog-zerolog v1.0.0 h1:YpRy0xux1uJr0Ng3wrEjv9nyvb4RAoNqkS611UjzeG8= +github.com/samber/slog-zerolog v1.0.0/go.mod h1:N2/g/mNGRY1zqsydIYE0uKipSSFsPDjytoVkRnZ0Jp0= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= @@ -511,8 +543,8 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= -github.com/sosodev/duration v1.1.0 h1:kQcaiGbJaIsRqgQy7VGlZrVw1giWO+lDoX3MCPnpVO4= -github.com/sosodev/duration v1.1.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/sosodev/duration v1.2.0 h1:pqK/FLSjsAADWY74SyWDCjOcd5l7H8GSnnOGEB9A1Us= +github.com/sosodev/duration v1.2.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -571,10 +603,28 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg= -go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM= -go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY= -go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4= +go.opentelemetry.io/contrib v1.21.1 h1:/U05KZ31iqMqAowhtW10cDPAViNY0tnpAacUgYBmuj8= +go.opentelemetry.io/contrib v1.21.1/go.mod h1:usW9bPlrjHiJFbK0a6yK/M5wNHs3nLmtrT3vzhoD3co= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM= +go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ= +go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8= +go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU= @@ -718,6 +768,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -725,8 +776,9 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -811,6 +863,12 @@ google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0 h1:YJ5pD9rF8o9Qtta0Cmy9rdBwkSjrTCT6XTiUQVOtIos= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -821,6 +879,8 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -833,8 +893,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/graphql/query.graphql b/graphql/query.graphql index 95b7bab..aab94cb 100644 --- a/graphql/query.graphql +++ b/graphql/query.graphql @@ -1,5 +1,6 @@ type Query { torrents(filter: TorrentsFilter, pagination: Pagination): [Torrent!]! + fsListDir(path: String!): [DirEntry!]! } input TorrentsFilter { @@ -40,4 +41,4 @@ input DateTimeFilter @oneOf { input BooleanFilter @oneOf { eq: Boolean -} \ No newline at end of file +} diff --git a/graphql/types/fs.graphql b/graphql/types/fs.graphql new file mode 100644 index 0000000..73ca119 --- /dev/null +++ b/graphql/types/fs.graphql @@ -0,0 +1,26 @@ +interface DirEntry { + name: String! +} + +type Dir implements DirEntry { + name: String! +} + +type File implements DirEntry { + name: String! + size: Int! +} + +type ResolverFS implements DirEntry { + name: String! +} + +type TorrentFS implements DirEntry { + name: String! + torrent: Torrent! +} + +type ArchiveFS implements DirEntry { + name: String! + size: Int! +} diff --git a/pkg/rlog/rlog.go b/pkg/rlog/rlog.go new file mode 100644 index 0000000..c8e1136 --- /dev/null +++ b/pkg/rlog/rlog.go @@ -0,0 +1,70 @@ +package rlog + +import ( + "log/slog" + "os" + + "github.com/rs/zerolog" + slogmulti "github.com/samber/slog-multi" + slogzerolog "github.com/samber/slog-zerolog" +) + +const errKey = "error" +const labelGroupKey = "labelGroup" + +var zl = zerolog.New(&zerolog.ConsoleWriter{Out: os.Stderr}) + +var handlers = []slog.Handler{ + slogzerolog.Option{Logger: &zl}.NewZerologHandler(), +} + +var defaultLogger = slog.New(slogmulti.Fanout(handlers...)) + +func init() { + slog.SetDefault(defaultLogger) +} + +func AddHandler(nh slog.Handler) { + handlers = append(handlers, nh) + defaultLogger = slog.New(slogmulti.Fanout(handlers...)) + slog.SetDefault(defaultLogger) +} + +func ComponentLog(name string) *slog.Logger { + return defaultLogger.With(slog.String("component", name)) +} + +func ServiceLog(name string) *slog.Logger { + return ComponentLog("service/" + name) +} + +func FunctionLog(log *slog.Logger, name string) *slog.Logger { + return log.With(slog.String("function", name)) +} + +func EndpointLog(log *slog.Logger, name string) *slog.Logger { + return log.With(slog.String("endpoint", name)) +} + +func Err(err error) slog.Attr { + return slog.Attr{Key: errKey, Value: fmtErr(err)} +} + +func Label(args ...any) slog.Attr { + return slog.Group(labelGroupKey, args...) +} + +// fmtErr returns a slog.GroupValue with keys "msg" and "trace". If the error +// does not implement interface { StackTrace() errors.StackTrace }, the "trace" +// key is omitted. +func fmtErr(err error) slog.Value { + if err == nil { + return slog.AnyValue(nil) + } + + var groupValues []slog.Attr + + groupValues = append(groupValues, slog.String("msg", err.Error())) + + return slog.GroupValue(groupValues...) +} diff --git a/src/config/model.go b/src/config/model.go index 88ec4b4..3aaf665 100644 --- a/src/config/model.go +++ b/src/config/model.go @@ -8,6 +8,8 @@ type Config struct { Log Log `koanf:"log"` SourceDir string `koanf:"source_dir"` + + OtelHttp string `koanf:"otel_http"` } type WebUi struct { diff --git a/src/http/api.go b/src/delivery/api.go similarity index 99% rename from src/http/api.go rename to src/delivery/api.go index 96fba6a..b9b032b 100644 --- a/src/http/api.go +++ b/src/delivery/api.go @@ -1,4 +1,4 @@ -package http +package delivery import ( "bytes" diff --git a/src/delivery/graphql/generated.go b/src/delivery/graphql/generated.go index a4f51df..2785488 100644 --- a/src/delivery/graphql/generated.go +++ b/src/delivery/graphql/generated.go @@ -52,10 +52,24 @@ type DirectiveRoot struct { } type ComplexityRoot struct { + ArchiveFS struct { + Name func(childComplexity int) int + Size func(childComplexity int) int + } + + Dir struct { + Name func(childComplexity int) int + } + DownloadTorrentResponse struct { Task func(childComplexity int) int } + File struct { + Name func(childComplexity int) int + Size func(childComplexity int) int + } + Mutation struct { CleanupTorrents func(childComplexity int, files *bool, dryRun bool) int DownloadTorrent func(childComplexity int, infohash string, file *string) int @@ -63,7 +77,12 @@ type ComplexityRoot struct { } Query struct { - Torrents func(childComplexity int, filter *model.TorrentsFilter, pagination *model.Pagination) int + FsListDir func(childComplexity int, path string) int + Torrents func(childComplexity int, filter *model.TorrentsFilter, pagination *model.Pagination) int + } + + ResolverFS struct { + Name func(childComplexity int) int } Schema struct { @@ -91,6 +110,11 @@ type ComplexityRoot struct { TorrentFilePath func(childComplexity int) int } + TorrentFS struct { + Name func(childComplexity int) int + Torrent func(childComplexity int) int + } + TorrentFile struct { BytesCompleted func(childComplexity int) int Filename func(childComplexity int) int @@ -119,6 +143,7 @@ type MutationResolver interface { } type QueryResolver interface { Torrents(ctx context.Context, filter *model.TorrentsFilter, pagination *model.Pagination) ([]*model.Torrent, error) + FsListDir(ctx context.Context, path string) ([]model.DirEntry, error) } type SubscriptionResolver interface { TaskProgress(ctx context.Context, taskID string) (<-chan model.Progress, error) @@ -151,6 +176,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in _ = ec switch typeName + "." + field { + case "ArchiveFS.name": + if e.complexity.ArchiveFS.Name == nil { + break + } + + return e.complexity.ArchiveFS.Name(childComplexity), true + + case "ArchiveFS.size": + if e.complexity.ArchiveFS.Size == nil { + break + } + + return e.complexity.ArchiveFS.Size(childComplexity), true + + case "Dir.name": + if e.complexity.Dir.Name == nil { + break + } + + return e.complexity.Dir.Name(childComplexity), true + case "DownloadTorrentResponse.task": if e.complexity.DownloadTorrentResponse.Task == nil { break @@ -158,6 +204,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.DownloadTorrentResponse.Task(childComplexity), true + case "File.name": + if e.complexity.File.Name == nil { + break + } + + return e.complexity.File.Name(childComplexity), true + + case "File.size": + if e.complexity.File.Size == nil { + break + } + + return e.complexity.File.Size(childComplexity), true + case "Mutation.cleanupTorrents": if e.complexity.Mutation.CleanupTorrents == nil { break @@ -194,6 +254,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.ValidateTorrents(childComplexity, args["filter"].(model.TorrentFilter)), true + case "Query.fsListDir": + if e.complexity.Query.FsListDir == nil { + break + } + + args, err := ec.field_Query_fsListDir_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.FsListDir(childComplexity, args["path"].(string)), true + case "Query.torrents": if e.complexity.Query.Torrents == nil { break @@ -206,6 +278,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Torrents(childComplexity, args["filter"].(*model.TorrentsFilter), args["pagination"].(*model.Pagination)), true + case "ResolverFS.name": + if e.complexity.ResolverFS.Name == nil { + break + } + + return e.complexity.ResolverFS.Name(childComplexity), true + case "Schema.mutation": if e.complexity.Schema.Mutation == nil { break @@ -302,6 +381,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Torrent.TorrentFilePath(childComplexity), true + case "TorrentFS.name": + if e.complexity.TorrentFS.Name == nil { + break + } + + return e.complexity.TorrentFS.Name(childComplexity), true + + case "TorrentFS.torrent": + if e.complexity.TorrentFS.Torrent == nil { + break + } + + return e.complexity.TorrentFS.Torrent(childComplexity), true + case "TorrentFile.bytesCompleted": if e.complexity.TorrentFile.BytesCompleted == nil { break @@ -530,6 +623,7 @@ type Task { }`, BuiltIn: false}, {Name: "../../../graphql/query.graphql", Input: `type Query { torrents(filter: TorrentsFilter, pagination: Pagination): [Torrent!]! + fsListDir(path: String!): [DirEntry!]! } input TorrentsFilter { @@ -570,7 +664,8 @@ input DateTimeFilter @oneOf { input BooleanFilter @oneOf { eq: Boolean -}`, BuiltIn: false}, +} +`, BuiltIn: false}, {Name: "../../../graphql/schema.graphql", Input: `directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION directive @stream on FIELD_DEFINITION @@ -580,7 +675,6 @@ type Schema { query: Query mutation: Mutation } - `, BuiltIn: false}, {Name: "../../../graphql/subscription.graphql", Input: `type Subscription { taskProgress(taskID: ID!): Progress @@ -598,6 +692,33 @@ interface Progress { current: Int! total: Int! }`, BuiltIn: false}, + {Name: "../../../graphql/types/fs.graphql", Input: `interface DirEntry { + name: String! +} + +type Dir implements DirEntry { + name: String! +} + +type File implements DirEntry { + name: String! + size: Int! +} + +type ResolverFS implements DirEntry { + name: String! +} + +type TorrentFS implements DirEntry { + name: String! + torrent: Torrent! +} + +type ArchiveFS implements DirEntry { + name: String! + size: Int! +} +`, BuiltIn: false}, {Name: "../../../graphql/types/torrent.graphql", Input: `type Torrent { name: String! infohash: String! @@ -707,6 +828,21 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs return args, nil } +func (ec *executionContext) field_Query_fsListDir_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["path"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("path")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["path"] = arg0 + return args, nil +} + func (ec *executionContext) field_Query_torrents_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -784,6 +920,138 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg // region **************************** field.gotpl ***************************** +func (ec *executionContext) _ArchiveFS_name(ctx context.Context, field graphql.CollectedField, obj *model.ArchiveFs) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ArchiveFS_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ArchiveFS_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ArchiveFS", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _ArchiveFS_size(ctx context.Context, field graphql.CollectedField, obj *model.ArchiveFs) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ArchiveFS_size(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Size, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int64) + fc.Result = res + return ec.marshalNInt2int64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ArchiveFS_size(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ArchiveFS", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Dir_name(ctx context.Context, field graphql.CollectedField, obj *model.Dir) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Dir_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Dir_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Dir", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _DownloadTorrentResponse_task(ctx context.Context, field graphql.CollectedField, obj *model.DownloadTorrentResponse) (ret graphql.Marshaler) { fc, err := ec.fieldContext_DownloadTorrentResponse_task(ctx, field) if err != nil { @@ -829,6 +1097,94 @@ func (ec *executionContext) fieldContext_DownloadTorrentResponse_task(ctx contex return fc, nil } +func (ec *executionContext) _File_name(ctx context.Context, field graphql.CollectedField, obj *model.File) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_File_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_File_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "File", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _File_size(ctx context.Context, field graphql.CollectedField, obj *model.File) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_File_size(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Size, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(int64) + fc.Result = res + return ec.marshalNInt2int64(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_File_size(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "File", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Mutation_validateTorrents(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation_validateTorrents(ctx, field) if err != nil { @@ -1068,6 +1424,61 @@ func (ec *executionContext) fieldContext_Query_torrents(ctx context.Context, fie return fc, nil } +func (ec *executionContext) _Query_fsListDir(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_fsListDir(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().FsListDir(rctx, fc.Args["path"].(string)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]model.DirEntry) + fc.Result = res + return ec.marshalNDirEntry2ᚕgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐDirEntryᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_fsListDir(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("FieldContext.Child cannot be called on type INTERFACE") + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Query_fsListDir_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query___type(ctx, field) if err != nil { @@ -1197,6 +1608,50 @@ func (ec *executionContext) fieldContext_Query___schema(ctx context.Context, fie return fc, nil } +func (ec *executionContext) _ResolverFS_name(ctx context.Context, field graphql.CollectedField, obj *model.ResolverFs) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_ResolverFS_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_ResolverFS_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "ResolverFS", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _Schema_query(ctx context.Context, field graphql.CollectedField, obj *model.Schema) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Schema_query(ctx, field) if err != nil { @@ -1224,6 +1679,8 @@ func (ec *executionContext) fieldContext_Schema_query(ctx context.Context, field switch field.Name { case "torrents": return ec.fieldContext_Query_torrents(ctx, field) + case "fsListDir": + return ec.fieldContext_Query_fsListDir(ctx, field) case "__schema": return ec.fieldContext_Query___schema(ctx, field) case "__type": @@ -1826,6 +2283,112 @@ func (ec *executionContext) fieldContext_Torrent_peers(ctx context.Context, fiel return fc, nil } +func (ec *executionContext) _TorrentFS_name(ctx context.Context, field graphql.CollectedField, obj *model.TorrentFs) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_TorrentFS_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_TorrentFS_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "TorrentFS", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _TorrentFS_torrent(ctx context.Context, field graphql.CollectedField, obj *model.TorrentFs) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_TorrentFS_torrent(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Torrent, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*model.Torrent) + fc.Result = res + return ec.marshalNTorrent2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐTorrent(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_TorrentFS_torrent(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "TorrentFS", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "name": + return ec.fieldContext_Torrent_name(ctx, field) + case "infohash": + return ec.fieldContext_Torrent_infohash(ctx, field) + case "bytesCompleted": + return ec.fieldContext_Torrent_bytesCompleted(ctx, field) + case "torrentFilePath": + return ec.fieldContext_Torrent_torrentFilePath(ctx, field) + case "bytesMissing": + return ec.fieldContext_Torrent_bytesMissing(ctx, field) + case "files": + return ec.fieldContext_Torrent_files(ctx, field) + case "excludedFiles": + return ec.fieldContext_Torrent_excludedFiles(ctx, field) + case "peers": + return ec.fieldContext_Torrent_peers(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Torrent", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _TorrentFile_filename(ctx context.Context, field graphql.CollectedField, obj *model.TorrentFile) (ret graphql.Marshaler) { fc, err := ec.fieldContext_TorrentFile_filename(ctx, field) if err != nil { @@ -4625,32 +5188,100 @@ func (ec *executionContext) unmarshalInputTorrentsFilter(ctx context.Context, ob switch k { case "name": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) - data, err := ec.unmarshalOStringFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐStringFilter(ctx, v) - if err != nil { - return it, err + directive0 := func(ctx context.Context) (interface{}, error) { + return ec.unmarshalOStringFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐStringFilter(ctx, v) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.OneOf == nil { + return nil, errors.New("directive oneOf is not implemented") + } + return ec.directives.OneOf(ctx, obj, directive0) + } + + tmp, err := directive1(ctx) + if err != nil { + return it, graphql.ErrorOnPath(ctx, err) + } + if data, ok := tmp.(*model.StringFilter); ok { + it.Name = data + } else if tmp == nil { + it.Name = nil + } else { + err := fmt.Errorf(`unexpected type %T from directive, should be *git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model.StringFilter`, tmp) + return it, graphql.ErrorOnPath(ctx, err) } - it.Name = data case "bytesCompleted": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("bytesCompleted")) - data, err := ec.unmarshalOIntFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐIntFilter(ctx, v) - if err != nil { - return it, err + directive0 := func(ctx context.Context) (interface{}, error) { + return ec.unmarshalOIntFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐIntFilter(ctx, v) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.OneOf == nil { + return nil, errors.New("directive oneOf is not implemented") + } + return ec.directives.OneOf(ctx, obj, directive0) + } + + tmp, err := directive1(ctx) + if err != nil { + return it, graphql.ErrorOnPath(ctx, err) + } + if data, ok := tmp.(*model.IntFilter); ok { + it.BytesCompleted = data + } else if tmp == nil { + it.BytesCompleted = nil + } else { + err := fmt.Errorf(`unexpected type %T from directive, should be *git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model.IntFilter`, tmp) + return it, graphql.ErrorOnPath(ctx, err) } - it.BytesCompleted = data case "bytesMissing": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("bytesMissing")) - data, err := ec.unmarshalOIntFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐIntFilter(ctx, v) - if err != nil { - return it, err + directive0 := func(ctx context.Context) (interface{}, error) { + return ec.unmarshalOIntFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐIntFilter(ctx, v) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.OneOf == nil { + return nil, errors.New("directive oneOf is not implemented") + } + return ec.directives.OneOf(ctx, obj, directive0) + } + + tmp, err := directive1(ctx) + if err != nil { + return it, graphql.ErrorOnPath(ctx, err) + } + if data, ok := tmp.(*model.IntFilter); ok { + it.BytesMissing = data + } else if tmp == nil { + it.BytesMissing = nil + } else { + err := fmt.Errorf(`unexpected type %T from directive, should be *git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model.IntFilter`, tmp) + return it, graphql.ErrorOnPath(ctx, err) } - it.BytesMissing = data case "peersCount": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("peersCount")) - data, err := ec.unmarshalOIntFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐIntFilter(ctx, v) - if err != nil { - return it, err + directive0 := func(ctx context.Context) (interface{}, error) { + return ec.unmarshalOIntFilter2ᚖgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐIntFilter(ctx, v) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.OneOf == nil { + return nil, errors.New("directive oneOf is not implemented") + } + return ec.directives.OneOf(ctx, obj, directive0) + } + + tmp, err := directive1(ctx) + if err != nil { + return it, graphql.ErrorOnPath(ctx, err) + } + if data, ok := tmp.(*model.IntFilter); ok { + it.PeersCount = data + } else if tmp == nil { + it.PeersCount = nil + } else { + err := fmt.Errorf(`unexpected type %T from directive, should be *git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model.IntFilter`, tmp) + return it, graphql.ErrorOnPath(ctx, err) } - it.PeersCount = data } } @@ -4661,6 +5292,50 @@ func (ec *executionContext) unmarshalInputTorrentsFilter(ctx context.Context, ob // region ************************** interface.gotpl *************************** +func (ec *executionContext) _DirEntry(ctx context.Context, sel ast.SelectionSet, obj model.DirEntry) graphql.Marshaler { + switch obj := (obj).(type) { + case nil: + return graphql.Null + case model.Dir: + return ec._Dir(ctx, sel, &obj) + case *model.Dir: + if obj == nil { + return graphql.Null + } + return ec._Dir(ctx, sel, obj) + case model.File: + return ec._File(ctx, sel, &obj) + case *model.File: + if obj == nil { + return graphql.Null + } + return ec._File(ctx, sel, obj) + case model.ResolverFs: + return ec._ResolverFS(ctx, sel, &obj) + case *model.ResolverFs: + if obj == nil { + return graphql.Null + } + return ec._ResolverFS(ctx, sel, obj) + case model.TorrentFs: + return ec._TorrentFS(ctx, sel, &obj) + case *model.TorrentFs: + if obj == nil { + return graphql.Null + } + return ec._TorrentFS(ctx, sel, obj) + case model.ArchiveFs: + return ec._ArchiveFS(ctx, sel, &obj) + case *model.ArchiveFs: + if obj == nil { + return graphql.Null + } + return ec._ArchiveFS(ctx, sel, obj) + default: + panic(fmt.Errorf("unexpected type %T", obj)) + } +} + func (ec *executionContext) _Progress(ctx context.Context, sel ast.SelectionSet, obj model.Progress) graphql.Marshaler { switch obj := (obj).(type) { case nil: @@ -4681,6 +5356,89 @@ func (ec *executionContext) _Progress(ctx context.Context, sel ast.SelectionSet, // region **************************** object.gotpl **************************** +var archiveFSImplementors = []string{"ArchiveFS", "DirEntry"} + +func (ec *executionContext) _ArchiveFS(ctx context.Context, sel ast.SelectionSet, obj *model.ArchiveFs) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, archiveFSImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ArchiveFS") + case "name": + out.Values[i] = ec._ArchiveFS_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "size": + out.Values[i] = ec._ArchiveFS_size(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var dirImplementors = []string{"Dir", "DirEntry"} + +func (ec *executionContext) _Dir(ctx context.Context, sel ast.SelectionSet, obj *model.Dir) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, dirImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Dir") + case "name": + out.Values[i] = ec._Dir_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var downloadTorrentResponseImplementors = []string{"DownloadTorrentResponse"} func (ec *executionContext) _DownloadTorrentResponse(ctx context.Context, sel ast.SelectionSet, obj *model.DownloadTorrentResponse) graphql.Marshaler { @@ -4717,6 +5475,50 @@ func (ec *executionContext) _DownloadTorrentResponse(ctx context.Context, sel as return out } +var fileImplementors = []string{"File", "DirEntry"} + +func (ec *executionContext) _File(ctx context.Context, sel ast.SelectionSet, obj *model.File) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, fileImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("File") + case "name": + out.Values[i] = ec._File_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "size": + out.Values[i] = ec._File_size(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var mutationImplementors = []string{"Mutation"} func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { @@ -4817,6 +5619,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "fsListDir": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_fsListDir(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "__type": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { @@ -4849,6 +5673,45 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr return out } +var resolverFSImplementors = []string{"ResolverFS", "DirEntry"} + +func (ec *executionContext) _ResolverFS(ctx context.Context, sel ast.SelectionSet, obj *model.ResolverFs) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, resolverFSImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ResolverFS") + case "name": + out.Values[i] = ec._ResolverFS_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var schemaImplementors = []string{"Schema"} func (ec *executionContext) _Schema(ctx context.Context, sel ast.SelectionSet, obj *model.Schema) graphql.Marshaler { @@ -5146,6 +6009,50 @@ func (ec *executionContext) _Torrent(ctx context.Context, sel ast.SelectionSet, return out } +var torrentFSImplementors = []string{"TorrentFS", "DirEntry"} + +func (ec *executionContext) _TorrentFS(ctx context.Context, sel ast.SelectionSet, obj *model.TorrentFs) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, torrentFSImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("TorrentFS") + case "name": + out.Values[i] = ec._TorrentFS_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "torrent": + out.Values[i] = ec._TorrentFS_torrent(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var torrentFileImplementors = []string{"TorrentFile"} func (ec *executionContext) _TorrentFile(ctx context.Context, sel ast.SelectionSet, obj *model.TorrentFile) graphql.Marshaler { @@ -5644,6 +6551,60 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se return res } +func (ec *executionContext) marshalNDirEntry2gitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐDirEntry(ctx context.Context, sel ast.SelectionSet, v model.DirEntry) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._DirEntry(ctx, sel, v) +} + +func (ec *executionContext) marshalNDirEntry2ᚕgitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐDirEntryᚄ(ctx context.Context, sel ast.SelectionSet, v []model.DirEntry) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNDirEntry2gitᚗkmsignᚗruᚋroyalcatᚋtstorᚋsrcᚋdeliveryᚋgraphqlᚋmodelᚐDirEntry(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) unmarshalNFloat2float64(ctx context.Context, v interface{}) (float64, error) { res, err := graphql.UnmarshalFloatContext(ctx, v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/src/delivery/graphql/model/models_gen.go b/src/delivery/graphql/model/models_gen.go index deb4b6d..4a56ee6 100644 --- a/src/delivery/graphql/model/models_gen.go +++ b/src/delivery/graphql/model/models_gen.go @@ -9,12 +9,25 @@ import ( "github.com/anacrolix/torrent" ) +type DirEntry interface { + IsDirEntry() + GetName() string +} + type Progress interface { IsProgress() GetCurrent() int64 GetTotal() int64 } +type ArchiveFs struct { + Name string `json:"name"` + Size int64 `json:"size"` +} + +func (ArchiveFs) IsDirEntry() {} +func (this ArchiveFs) GetName() string { return this.Name } + type BooleanFilter struct { Eq *bool `json:"eq,omitempty"` } @@ -27,10 +40,25 @@ type DateTimeFilter struct { Lte *time.Time `json:"lte,omitempty"` } +type Dir struct { + Name string `json:"name"` +} + +func (Dir) IsDirEntry() {} +func (this Dir) GetName() string { return this.Name } + type DownloadTorrentResponse struct { Task *Task `json:"task,omitempty"` } +type File struct { + Name string `json:"name"` + Size int64 `json:"size"` +} + +func (File) IsDirEntry() {} +func (this File) GetName() string { return this.Name } + type IntFilter struct { Eq *int64 `json:"eq,omitempty"` Gt *int64 `json:"gt,omitempty"` @@ -51,6 +79,13 @@ type Pagination struct { type Query struct { } +type ResolverFs struct { + Name string `json:"name"` +} + +func (ResolverFs) IsDirEntry() {} +func (this ResolverFs) GetName() string { return this.Name } + type Schema struct { Query *Query `json:"query,omitempty"` Mutation *Mutation `json:"mutation,omitempty"` @@ -81,6 +116,14 @@ type Torrent struct { T *controller.Torrent `json:"-"` } +type TorrentFs struct { + Name string `json:"name"` + Torrent *Torrent `json:"torrent"` +} + +func (TorrentFs) IsDirEntry() {} +func (this TorrentFs) GetName() string { return this.Name } + type TorrentFile struct { Filename string `json:"filename"` Size int64 `json:"size"` diff --git a/src/delivery/graphql/resolver/query.resolvers.go b/src/delivery/graphql/resolver/query.resolvers.go index b4687a7..30ee852 100644 --- a/src/delivery/graphql/resolver/query.resolvers.go +++ b/src/delivery/graphql/resolver/query.resolvers.go @@ -9,6 +9,7 @@ import ( graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql" "git.kmsign.ru/royalcat/tstor/src/delivery/graphql/model" + "git.kmsign.ru/royalcat/tstor/src/host/vfs" ) // Torrents is the resolver for the torrents field. @@ -63,6 +64,42 @@ func (r *queryResolver) Torrents(ctx context.Context, filter *model.TorrentsFilt return tr, nil } +// FsListDir is the resolver for the fsListDir field. +func (r *queryResolver) FsListDir(ctx context.Context, path string) ([]model.DirEntry, error) { + entries, err := r.VFS.ReadDir(path) + if err != nil { + return nil, err + } + out := []model.DirEntry{} + for _, e := range entries { + switch e.(type) { + case *vfs.TorrentFs: + e := e.(*vfs.TorrentFs) + out = append(out, model.TorrentFs{ + Name: e.Name(), + Torrent: model.MapTorrent(e.Torrent), + }) + default: + if e.IsDir() { + out = append(out, model.Dir{Name: e.Name()}) + } else { + info, err := e.Info() + if err != nil { + return nil, err + } + + out = append(out, model.File{ + Name: e.Name(), + Size: info.Size(), + }) + } + } + + } + + return out, nil +} + // Query returns graph.QueryResolver implementation. func (r *Resolver) Query() graph.QueryResolver { return &queryResolver{r} } diff --git a/src/delivery/graphql/resolver/resolver.go b/src/delivery/graphql/resolver/resolver.go index 1e937e1..9a76120 100644 --- a/src/delivery/graphql/resolver/resolver.go +++ b/src/delivery/graphql/resolver/resolver.go @@ -1,6 +1,9 @@ package resolver -import "git.kmsign.ru/royalcat/tstor/src/host/service" +import ( + "git.kmsign.ru/royalcat/tstor/src/host/service" + "git.kmsign.ru/royalcat/tstor/src/host/vfs" +) // This file will not be regenerated automatically. // @@ -8,4 +11,5 @@ import "git.kmsign.ru/royalcat/tstor/src/host/service" type Resolver struct { Service *service.Service + VFS vfs.Filesystem } diff --git a/src/http/http.go b/src/delivery/http.go similarity index 91% rename from src/http/http.go rename to src/delivery/http.go index 495949a..244ec16 100644 --- a/src/http/http.go +++ b/src/delivery/http.go @@ -1,4 +1,4 @@ -package http +package delivery import ( "fmt" @@ -7,15 +7,15 @@ import ( "git.kmsign.ru/royalcat/tstor" "git.kmsign.ru/royalcat/tstor/src/config" - "git.kmsign.ru/royalcat/tstor/src/delivery" "git.kmsign.ru/royalcat/tstor/src/host/service" + "git.kmsign.ru/royalcat/tstor/src/host/vfs" "github.com/anacrolix/missinggo/v2/filecache" "github.com/gin-contrib/pprof" "github.com/gin-gonic/gin" "github.com/shurcooL/httpfs/html/vfstemplate" ) -func New(fc *filecache.Cache, ss *service.Stats, s *service.Service, logPath string, cfg *config.Config) error { +func New(fc *filecache.Cache, ss *service.Stats, s *service.Service, vfs vfs.Filesystem, logPath string, cfg *config.Config) error { log := slog.With() gin.SetMode(gin.ReleaseMode) @@ -40,18 +40,16 @@ func New(fc *filecache.Cache, ss *service.Stats, s *service.Service, logPath str // r.GET("/routes", routesHandler(ss)) r.GET("/logs", logsHandler) r.GET("/servers", serversFoldersHandler()) - r.Any("/graphql", gin.WrapH(delivery.GraphQLHandler(s))) + r.Any("/graphql", gin.WrapH(GraphQLHandler(s, vfs))) api := r.Group("/api") { api.GET("/log", apiLogHandler(logPath)) api.GET("/status", apiStatusHandler(fc, ss)) // api.GET("/servers", apiServersHandler(tss)) - // api.GET("/routes", apiRoutesHandler(ss)) // api.POST("/routes/:route/torrent", apiAddTorrentHandler(s)) // api.DELETE("/routes/:route/torrent/:torrent_hash", apiDelTorrentHandler(s)) - } log.Info("starting webserver", "host", fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)) diff --git a/src/http/model.go b/src/delivery/model.go similarity index 88% rename from src/http/model.go rename to src/delivery/model.go index c752eb0..3088d80 100644 --- a/src/http/model.go +++ b/src/delivery/model.go @@ -1,4 +1,4 @@ -package http +package delivery type RouteAdd struct { Magnet string `json:"magnet" binding:"required"` diff --git a/src/delivery/router.go b/src/delivery/router.go index 4c5f0c4..d03b2ff 100644 --- a/src/delivery/router.go +++ b/src/delivery/router.go @@ -6,23 +6,26 @@ import ( graph "git.kmsign.ru/royalcat/tstor/src/delivery/graphql" "git.kmsign.ru/royalcat/tstor/src/delivery/graphql/resolver" "git.kmsign.ru/royalcat/tstor/src/host/service" + "git.kmsign.ru/royalcat/tstor/src/host/vfs" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/extension" "github.com/99designs/gqlgen/graphql/handler/lru" "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/ravilushqa/otelgqlgen" ) -func GraphQLHandler(service *service.Service) http.Handler { +func GraphQLHandler(service *service.Service, vfs vfs.Filesystem) http.Handler { graphqlHandler := handler.NewDefaultServer( graph.NewExecutableSchema( graph.Config{ - Resolvers: &resolver.Resolver{Service: service}, + Resolvers: &resolver.Resolver{Service: service, VFS: vfs}, Directives: graph.DirectiveRoot{ OneOf: graph.OneOf, }, }, ), ) + graphqlHandler.AddTransport(&transport.POST{}) graphqlHandler.AddTransport(&transport.Websocket{}) graphqlHandler.AddTransport(&transport.SSE{}) @@ -30,6 +33,7 @@ func GraphQLHandler(service *service.Service) http.Handler { graphqlHandler.SetQueryCache(lru.New(1000)) graphqlHandler.Use(extension.Introspection{}) graphqlHandler.Use(extension.AutomaticPersistedQuery{Cache: lru.New(100)}) + graphqlHandler.Use(otelgqlgen.Middleware()) return graphqlHandler } diff --git a/src/http/web.go b/src/delivery/web.go similarity index 96% rename from src/http/web.go rename to src/delivery/web.go index 860347f..9bcc202 100644 --- a/src/http/web.go +++ b/src/delivery/web.go @@ -1,4 +1,4 @@ -package http +package delivery import ( "net/http" diff --git a/src/host/service/service.go b/src/host/service/service.go index 4388058..1d42a6a 100644 --- a/src/host/service/service.go +++ b/src/host/service/service.go @@ -5,24 +5,33 @@ import ( "fmt" "log/slog" "os" + "path" "path/filepath" "slices" "strings" "time" + "git.kmsign.ru/royalcat/tstor/src/config" "git.kmsign.ru/royalcat/tstor/src/host/controller" "git.kmsign.ru/royalcat/tstor/src/host/datastorage" "git.kmsign.ru/royalcat/tstor/src/host/store" "git.kmsign.ru/royalcat/tstor/src/host/vfs" "go.uber.org/multierr" + "golang.org/x/exp/maps" "github.com/anacrolix/torrent" "github.com/anacrolix/torrent/bencode" "github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/types" "github.com/anacrolix/torrent/types/infohash" + "github.com/royalcat/kv" ) +type DirAquire struct { + Name string + Hashes []infohash.T +} + type Service struct { c *torrent.Client excludedFiles *store.FilesMappings @@ -35,11 +44,21 @@ type Service struct { Storage datastorage.DataStorage SourceDir string + dirsAquire kv.Store[string, DirAquire] + log *slog.Logger addTimeout, readTimeout int } -func NewService(sourceDir string, c *torrent.Client, storage datastorage.DataStorage, excludedFiles *store.FilesMappings, infoBytes *store.InfoBytes, addTimeout, readTimeout int) *Service { +func NewService(sourceDir string, cfg config.TorrentClient, c *torrent.Client, + storage datastorage.DataStorage, excludedFiles *store.FilesMappings, infoBytes *store.InfoBytes, + addTimeout, readTimeout int, +) (*Service, error) { + dirsAcquire, err := kv.NewBadgerKV[string, DirAquire](path.Join(cfg.MetadataFolder, "dir-acquire")) + if err != nil { + return nil, err + } + s := &Service{ log: slog.With("component", "torrent-service"), c: c, @@ -49,6 +68,7 @@ func NewService(sourceDir string, c *torrent.Client, storage datastorage.DataSto Storage: storage, SourceDir: sourceDir, torrentLoaded: make(chan struct{}), + dirsAquire: dirsAcquire, // stats: newStats(), // TODO persistent addTimeout: addTimeout, readTimeout: readTimeout, @@ -62,7 +82,7 @@ func NewService(sourceDir string, c *torrent.Client, storage datastorage.DataSto close(s.torrentLoaded) }() - return s + return s, nil } var _ vfs.FsFactory = (*Service)(nil).NewTorrentFs @@ -109,17 +129,16 @@ func (s *Service) AddTorrent(ctx context.Context, f vfs.File) (*torrent.Torrent, if err != nil { infoBytes = nil } else { - // for _, t := range s.c.Torrents() { - // if t.Name() == info.BestName() && t.InfoHash() != spec.InfoHash { - // <-t.GotInfo() - // if !isTorrentCompatable(*t.Info(), info) { - // return nil, fmt.Errorf( - // "torrent with name '%s' not compatable existing infohash: %s, new: %s", - // t.Name(), t.InfoHash().HexString(), spec.InfoHash.HexString(), - // ) - // } - // } - // } + compatable, _, err := s.checkTorrentCompatable(ctx, spec.InfoHash, info) + if err != nil { + return nil, err + } + if !compatable { + return nil, fmt.Errorf( + "torrent with name '%s' not compatable existing infohash: %s, new: %s", + t.Name(), t.InfoHash().HexString(), spec.InfoHash.HexString(), + ) + } } t, _ = s.c.AddTorrentOpt(torrent.AddTorrentOpts{ @@ -149,9 +168,67 @@ func (s *Service) AddTorrent(ctx context.Context, f vfs.File) (*torrent.Torrent, return t, nil } -func isTorrentCompatable(existingInfo, newInfo metainfo.Info) bool { - existingFiles := slices.Clone(existingInfo.Files) - newFiles := slices.Clone(newInfo.Files) +func (s *Service) checkTorrentCompatable(ctx context.Context, ih infohash.T, info metainfo.Info) (compatable bool, tryLater bool, err error) { + log := s.log.With("new-name", info.BestName(), "new-infohash", ih.String()) + + name := info.BestName() + + aq, found, err := s.dirsAquire.Get(ctx, info.BestName()) + if err != nil { + return false, false, err + } + if !found { + err = s.dirsAquire.Set(ctx, name, DirAquire{ + Name: name, + Hashes: slices.Compact([]infohash.T{ih}), + }) + if err != nil { + return false, false, err + } + + log.Debug("acquiring was not found, so created") + return true, false, nil + } + + if slices.Contains(aq.Hashes, ih) { + log.Debug("hash already know to be compatable") + return true, false, nil + } + + for _, existingTorrent := range s.c.Torrents() { + if existingTorrent.Name() != name || existingTorrent.InfoHash() == ih { + continue + } + + existingInfo := existingTorrent.Info() + + existingFiles := slices.Clone(existingInfo.Files) + newFiles := slices.Clone(info.Files) + + if !s.checkTorrentFilesCompatable(aq, existingFiles, newFiles) { + return false, false, nil + } + + aq.Hashes = slicesUnique(append(aq.Hashes, ih)) + err = s.dirsAquire.Set(ctx, aq.Name, aq) + if err != nil { + log.Warn("torrent not compatible") + return false, false, err + } + + } + + if slices.Contains(aq.Hashes, ih) { + log.Debug("hash is compatable") + return true, false, nil + } + + log.Debug("torrent with same name not found, try later") + return false, true, nil +} + +func (s *Service) checkTorrentFilesCompatable(aq DirAquire, existingFiles, newFiles []metainfo.FileInfo) bool { + log := s.log.With("name", aq.Name) pathCmp := func(a, b metainfo.FileInfo) int { return slices.Compare(a.BestPath(), b.BestPath()) @@ -167,14 +244,45 @@ func isTorrentCompatable(existingInfo, newInfo metainfo.Info) bool { } if len(newFiles) > len(existingFiles) { - all := append(existingFiles, newFiles...) - slices.SortStableFunc(all, pathCmp) - slices.CompactFunc(all, func(fi1, fi2 metainfo.FileInfo) bool { - return slices.Equal(fi1.BestPath(), fi2.BestPath()) && fi1.Length == fi2.Length - }) + type fileInfo struct { + Path string + Length int64 + } + mapInfo := func(fi metainfo.FileInfo) fileInfo { + return fileInfo{ + Path: strings.Join(fi.BestPath(), "/"), + Length: fi.Length, + } + } + + existingFiles := apply(existingFiles, mapInfo) + newFiles := apply(newFiles, mapInfo) + + for _, n := range newFiles { + if slices.Contains(existingFiles, n) { + continue + } + + for _, e := range existingFiles { + if e.Path == n.Path && e.Length != n.Length { + log.Warn("torrents not compatible, has files with different length", "path", n.Path, "existing-length", e.Length, "new-length", e.Length) + return false + } + } + } } - return false + return true +} + +func (s *Service) getTorrentsByName(name string) []*torrent.Torrent { + out := []*torrent.Torrent{} + for _, t := range s.c.Torrents() { + if t.Name() == name { + out = append(out, t) + } + } + return out } func isValidInfoHashBytes(d []byte) bool { @@ -188,12 +296,17 @@ func (s *Service) NewTorrentFs(f vfs.File) (vfs.Filesystem, error) { defer cancel() defer f.Close() + info, err := f.Stat() + if err != nil { + return nil, err + } + t, err := s.AddTorrent(ctx, f) if err != nil { return nil, err } - return vfs.NewTorrentFs(controller.NewTorrent(t, s.excludedFiles), s.readTimeout), nil + return vfs.NewTorrentFs(info.Name(), controller.NewTorrent(t, s.excludedFiles), s.readTimeout), nil } func (s *Service) Stats() (*Stats, error) { @@ -252,3 +365,20 @@ func (s *Service) GetTorrent(infohashHex string) (*controller.Torrent, error) { return controller.NewTorrent(t, s.excludedFiles), nil } + +func slicesUnique[S ~[]E, E comparable](in S) S { + m := map[E]struct{}{} + for _, v := range in { + m[v] = struct{}{} + } + + return maps.Keys(m) +} + +func apply[I, O any](in []I, f func(e I) O) []O { + out := []O{} + for _, v := range in { + out = append(out, f(v)) + } + return out +} diff --git a/src/host/vfs/archive.go b/src/host/vfs/archive.go index d0708fa..3e28bfd 100644 --- a/src/host/vfs/archive.go +++ b/src/host/vfs/archive.go @@ -40,9 +40,9 @@ var ArchiveFactories = map[string]FsFactory{ type archiveLoader func(r iio.Reader, size int64) (map[string]*archiveFile, error) -var _ Filesystem = &archive{} +var _ Filesystem = &ArchiveFS{} -type archive struct { +type ArchiveFS struct { name string r iio.Reader @@ -52,8 +52,8 @@ type archive struct { files func() (map[string]File, error) } -func NewArchive(name string, r iio.Reader, size int64, loader archiveLoader) *archive { - return &archive{ +func NewArchive(name string, r iio.Reader, size int64, loader archiveLoader) *ArchiveFS { + return &ArchiveFS{ name: name, r: r, size: size, @@ -94,11 +94,11 @@ func NewArchive(name string, r iio.Reader, size int64, loader archiveLoader) *ar } // Unlink implements Filesystem. -func (a *archive) Unlink(filename string) error { +func (a *ArchiveFS) Unlink(filename string) error { return ErrNotImplemented } -func (a *archive) Open(filename string) (File, error) { +func (a *ArchiveFS) Open(filename string) (File, error) { files, err := a.files() if err != nil { return nil, err @@ -107,7 +107,7 @@ func (a *archive) Open(filename string) (File, error) { return getFile(files, filename) } -func (fs *archive) ReadDir(path string) ([]fs.DirEntry, error) { +func (fs *ArchiveFS) ReadDir(path string) ([]fs.DirEntry, error) { files, err := fs.files() if err != nil { return nil, err @@ -117,7 +117,7 @@ func (fs *archive) ReadDir(path string) ([]fs.DirEntry, error) { } // Stat implements Filesystem. -func (afs *archive) Stat(filename string) (fs.FileInfo, error) { +func (afs *ArchiveFS) Stat(filename string) (fs.FileInfo, error) { files, err := afs.files() if err != nil { return nil, err diff --git a/src/host/vfs/resolver.go b/src/host/vfs/resolver.go index a51957f..79ada38 100644 --- a/src/host/vfs/resolver.go +++ b/src/host/vfs/resolver.go @@ -51,7 +51,21 @@ func (r *ResolveFS) ReadDir(dir string) ([]fs.DirEntry, error) { out := make([]fs.DirEntry, 0, len(entries)) for _, e := range entries { if r.resolver.isNestedFs(e.Name()) { - out = append(out, newDirInfo(e.Name())) + filepath := path.Join(dir, e.Name()) + file, err := r.Open(filepath) + if err != nil { + return nil, err + } + nfs, err := r.resolver.nestedFs(filepath, file) + if err != nil { + return nil, err + } + + if e, ok := nfs.(fs.DirEntry); ok { + out = append(out, e) + } else { + out = append(out, newDirInfo(e.Name())) + } } else { out = append(out, e) } diff --git a/src/host/vfs/resolver_test.go b/src/host/vfs/resolver_test.go index aa6882e..01eb77a 100644 --- a/src/host/vfs/resolver_test.go +++ b/src/host/vfs/resolver_test.go @@ -83,7 +83,7 @@ func TestResolver(t *testing.T) { require.NoError(err) require.Equal("/f1.rar", fsPath) require.Equal("/f2.rar", nestedFsPath) - require.IsType(&archive{}, nestedFs) + require.IsType(&ArchiveFS{}, nestedFs) }) t.Run("root", func(t *testing.T) { t.Parallel() @@ -123,7 +123,7 @@ func TestResolver(t *testing.T) { require.NoError(err) require.Equal("/f1.rar", fsPath) require.Equal("/", nestedFsPath) - require.IsType(&archive{}, nestedFs) + require.IsType(&ArchiveFS{}, nestedFs) }) t.Run("inside folder", func(t *testing.T) { t.Parallel() @@ -134,7 +134,7 @@ func TestResolver(t *testing.T) { return &Dummy{}, nil }) require.NoError(err) - require.IsType(&archive{}, nestedFs) + require.IsType(&ArchiveFS{}, nestedFs) require.Equal("/test1/f1.rar", fsPath) require.Equal("/", nestedFsPath) }) diff --git a/src/host/vfs/torrent.go b/src/host/vfs/torrent.go index 0bbe3b6..4b51e9d 100644 --- a/src/host/vfs/torrent.go +++ b/src/host/vfs/torrent.go @@ -21,8 +21,10 @@ import ( var _ Filesystem = &TorrentFs{} type TorrentFs struct { - mu sync.Mutex - c *controller.Torrent + name string + + mu sync.Mutex + Torrent *controller.Torrent readTimeout int @@ -31,14 +33,37 @@ type TorrentFs struct { resolver *resolver } -func NewTorrentFs(c *controller.Torrent, readTimeout int) *TorrentFs { +func NewTorrentFs(name string, c *controller.Torrent, readTimeout int) *TorrentFs { return &TorrentFs{ - c: c, + name: name, + Torrent: c, readTimeout: readTimeout, resolver: newResolver(ArchiveFactories), } } +var _ fs.DirEntry = (*TorrentFs)(nil) + +// Name implements fs.DirEntry. +func (tfs *TorrentFs) Name() string { + return tfs.name +} + +// Info implements fs.DirEntry. +func (tfs *TorrentFs) Info() (fs.FileInfo, error) { + return newDirInfo(tfs.name), nil +} + +// IsDir implements fs.DirEntry. +func (tfs *TorrentFs) IsDir() bool { + return true +} + +// Type implements fs.DirEntry. +func (tfs *TorrentFs) Type() fs.FileMode { + return fs.ModeDir +} + func (fs *TorrentFs) files() (map[string]File, error) { fs.mu.Lock() defer fs.mu.Unlock() @@ -47,7 +72,7 @@ func (fs *TorrentFs) files() (map[string]File, error) { return fs.filesCache, nil } - files, err := fs.c.Files(context.Background()) + files, err := fs.Torrent.Files(context.Background()) if err != nil { return nil, err } @@ -65,8 +90,8 @@ func (fs *TorrentFs) files() (map[string]File, error) { } // TODO optional - if len(fs.filesCache) == 1 && fs.resolver.isNestedFs(fs.c.Name()) { - filepath := "/" + fs.c.Name() + if len(fs.filesCache) == 1 && fs.resolver.isNestedFs(fs.Torrent.Name()) { + filepath := "/" + fs.Torrent.Name() if file, ok := fs.filesCache[filepath]; ok { nestedFs, err := fs.resolver.nestedFs(filepath, file) if err != nil { @@ -86,7 +111,7 @@ func (fs *TorrentFs) files() (map[string]File, error) { } DEFAULT_DIR: - rootDir := "/" + fs.c.Name() + "/" + rootDir := "/" + fs.Torrent.Name() + "/" singleDir := true for k, _ := range fs.filesCache { if !strings.HasPrefix(k, rootDir) { @@ -238,7 +263,7 @@ func (fs *TorrentFs) Unlink(name string) error { return ErrNotImplemented } - return fs.c.ExcludeFile(context.Background(), tfile.file) + return fs.Torrent.ExcludeFile(context.Background(), tfile.file) } type reader interface { diff --git a/src/log/log.go b/src/log/log.go index 654de65..6d0bcdb 100644 --- a/src/log/log.go +++ b/src/log/log.go @@ -1,30 +1,21 @@ package log -import ( - "log/slog" - "os" - "time" - - "git.kmsign.ru/royalcat/tstor/src/config" - "github.com/lmittmann/tint" -) - const FileName = "tstor.log" -func Load(config *config.Log) { - level := slog.LevelInfo - if config.Debug { - level = slog.LevelDebug - } +// func Load(config *config.Log) { +// level := slog.LevelInfo +// if config.Debug { +// level = slog.LevelDebug +// } - slog.SetDefault(slog.New( - tint.NewHandler(os.Stdout, &tint.Options{ - Level: level, - TimeFormat: time.Kitchen, - // NoColor: !isatty.IsTerminal(os.Stdout.Fd()), - }), - )) -} +// slog.SetDefault(slog.New( +// tint.NewHandler(os.Stdout, &tint.Options{ +// Level: level, +// TimeFormat: time.Kitchen, +// // NoColor: !isatty.IsTerminal(os.Stdout.Fd()), +// }), +// )) +// } // func newRollingFile(config *config.Log) io.Writer { // if err := os.MkdirAll(config.Path, 0744); err != nil { diff --git a/src/telemetry/setup.go b/src/telemetry/setup.go new file mode 100644 index 0000000..5e86ba0 --- /dev/null +++ b/src/telemetry/setup.go @@ -0,0 +1,126 @@ +package telemetry + +import ( + "context" + "log/slog" + "os" + + "git.kmsign.ru/royalcat/tstor/pkg/rlog" + "github.com/agoda-com/opentelemetry-go/otelslog" + "github.com/agoda-com/opentelemetry-logs-go/exporters/otlp/otlplogs" + "github.com/agoda-com/opentelemetry-logs-go/exporters/otlp/otlplogs/otlplogshttp" + logsdk "github.com/agoda-com/opentelemetry-logs-go/sdk/logs" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/exporters/prometheus" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.24.0" +) + +type Client struct { + log *slog.Logger + + tracerProvider *trace.TracerProvider + metricProvider *metric.MeterProvider + loggerProvider *logsdk.LoggerProvider +} + +func (client *Client) Shutdown(ctx context.Context) { + log := rlog.FunctionLog(client.log, "Shutdown") + if client.metricProvider == nil { + err := client.metricProvider.Shutdown(ctx) + if err != nil { + log.Error("error shutting down metric provider", rlog.Err(err)) + } + } + if client.tracerProvider == nil { + err := client.tracerProvider.Shutdown(ctx) + if err != nil { + log.Error("error shutting down tracer provider", rlog.Err(err)) + } + } + if client.loggerProvider == nil { + err := client.loggerProvider.Shutdown(ctx) + if err != nil { + log.Error("error shutting down logger provider", rlog.Err(err)) + } + } +} + +const appName = "tstor" + +func Setup(ctx context.Context, endpoint string) (*Client, error) { + log := rlog.ComponentLog("telemetry") + + client := &Client{ + log: log, + } + otel.SetErrorHandler(otel.ErrorHandlerFunc(func(cause error) { + log.Error("otel error", rlog.Err(cause)) + })) + + hostName, _ := os.Hostname() + + r, err := resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName(appName), + semconv.HostName(hostName), + ), + ) + if err != nil { + return nil, err + } + + metricExporter, err := prometheus.New(prometheus.WithNamespace(appName)) + if err != nil { + return nil, err + } + client.metricProvider = metric.NewMeterProvider( + metric.WithReader(metricExporter), + metric.WithResource(r), + ) + otel.SetMeterProvider(client.metricProvider) + log.Info("prometheus metrics provider initialized") + + traceExporter, err := otlptracehttp.New(ctx, + otlptracehttp.WithEndpoint(endpoint), + otlptracehttp.WithRetry(otlptracehttp.RetryConfig{ + Enabled: false, + }), + ) + if err != nil { + return nil, err + } + client.tracerProvider = trace.NewTracerProvider( + trace.WithBatcher(traceExporter), + trace.WithResource(r), + ) + otel.SetTracerProvider(client.tracerProvider) + log.Info("otel tracing provider initialized") + + logExporter, err := otlplogs.NewExporter(ctx, + otlplogs.WithClient( + otlplogshttp.NewClient(otlplogshttp.WithEndpoint(endpoint)), + ), + ) + if err != nil { + return nil, err + } + client.loggerProvider = logsdk.NewLoggerProvider( + logsdk.WithBatcher(logExporter), + logsdk.WithResource(r), + ) + + rlog.AddHandler(otelslog.NewOtelHandler(client.loggerProvider, + &otelslog.HandlerOptions{ + Level: slog.LevelDebug, + }), + ) + client.log = slog.Default() + + return client, nil +}