diff --git a/Dockerfile b/Dockerfile
index 9d533da..054e197 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,7 +11,10 @@ COPY ./src ./src
 COPY ./cmd ./cmd
 
 ARG TARGETOS TARGETARCH
-RUN --mount=type=cache,mode=0777,target=/go/pkg/mod CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -tags timetzdata -o /tstor ./cmd/tstor/main.go 
+RUN --mount=type=cache,mode=0777,target=/go/pkg/mod \
+    CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH \
+    go build -tags timetzdata \
+    -o /tstor ./cmd/tstor/main.go 
 
 FROM scratch
 
diff --git a/cmd/tstor/main.go b/cmd/tstor/main.go
index 23e874e..6673f89 100644
--- a/cmd/tstor/main.go
+++ b/cmd/tstor/main.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"log/slog"
 
@@ -75,23 +76,22 @@ func run(configPath string) error {
 	if err != nil {
 		return err
 	}
-
 	defer client.Shutdown(ctx)
 
 	log := rlog.Component("run")
 
 	// TODO make optional
-	err = syscall.Setpriority(syscall.PRIO_PGRP, 0, 19)
-	if err != nil {
-		log.Error(ctx, "set priority failed", rlog.Error(err))
-	}
+	// err = syscall.Setpriority(syscall.PRIO_PGRP, 0, 19)
+	// if err != nil {
+	// 	log.Error(ctx, "set priority failed", rlog.Error(err))
+	// }
 
 	if err := os.MkdirAll(conf.SourceDir, 0744); err != nil {
 		return fmt.Errorf("error creating data folder: %w", err)
 	}
 
 	sourceFs := osfs.New(conf.SourceDir, osfs.WithBoundOS())
-	tsrv, err := torrent.NewService(sourceFs, conf.TorrentClient)
+	tsrv, err := torrent.NewDaemon(sourceFs, conf.TorrentClient)
 	if err != nil {
 		return fmt.Errorf("error creating service: %w", err)
 	}
@@ -187,7 +187,7 @@ func run(configPath string) error {
 	go func() {
 		logFilename := filepath.Join(conf.Log.Path, "logs")
 
-		err := delivery.New(nil, tsrv, sfs, logFilename, conf)
+		err := delivery.Run(tsrv, sfs, logFilename, conf)
 		if err != nil {
 			log.Error(ctx, "error initializing HTTP server", rlog.Error(err))
 		}
@@ -197,5 +197,7 @@ func run(configPath string) error {
 	signal.Notify(sigChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
 	<-sigChan
 
-	return tsrv.Close(ctx)
+	return errors.Join(
+		tsrv.Close(ctx),
+	)
 }
diff --git a/go.mod b/go.mod
index 9a275e6..e949487 100644
--- a/go.mod
+++ b/go.mod
@@ -2,27 +2,27 @@ module git.kmsign.ru/royalcat/tstor
 
 go 1.22.3
 
+replace github.com/bytedance/sonic v1.11.9 => github.com/bytedance/sonic v1.12.1
+
 require (
 	github.com/99designs/gqlgen v0.17.49
 	github.com/agoda-com/opentelemetry-go/otelslog v0.1.1
 	github.com/agoda-com/opentelemetry-logs-go v0.5.0
 	github.com/anacrolix/dht/v2 v2.21.1
-	github.com/anacrolix/log v0.15.2
-	github.com/anacrolix/missinggo/v2 v2.7.3
-	github.com/anacrolix/torrent v1.56.1
+	github.com/anacrolix/log v0.15.3-0.20240627045001-cd912c641d83
+	github.com/anacrolix/torrent v1.56.2-0.20240813010934-f4711825e84e
 	github.com/billziss-gh/cgofuse v1.5.0
 	github.com/bodgit/sevenzip v1.5.1
-	github.com/cyphar/filepath-securejoin v0.2.5
 	github.com/dgraph-io/badger/v4 v4.2.0
 	github.com/dustin/go-humanize v1.0.1
 	github.com/gin-gonic/gin v1.9.1
 	github.com/go-git/go-billy/v5 v5.5.0
 	github.com/gofrs/uuid/v5 v5.1.0
+	github.com/google/go-github/v63 v63.0.0
 	github.com/google/uuid v1.6.0
 	github.com/grafana/otel-profiling-go v0.5.1
-	github.com/grafana/pyroscope-go v1.1.1
+	github.com/grafana/pyroscope-go v1.1.2
 	github.com/hashicorp/golang-lru/v2 v2.0.7
-	github.com/iceber/iouring-go v0.0.0-20230403020409-002cfd2e2a90
 	github.com/knadh/koanf/parsers/yaml v0.1.0
 	github.com/knadh/koanf/providers/env v0.1.0
 	github.com/knadh/koanf/providers/file v0.1.0
@@ -32,6 +32,7 @@ require (
 	github.com/labstack/echo/v4 v4.12.0
 	github.com/mattetti/filebuffer v1.0.1
 	github.com/nwaples/rardecode/v2 v2.0.0-beta.2
+	github.com/prometheus/client_golang v1.19.1
 	github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93
 	github.com/ravilushqa/otelgqlgen v0.15.0
 	github.com/royalcat/ctxio v0.0.0-20240602060200-590d464c39be
@@ -46,17 +47,19 @@ require (
 	github.com/vektah/gqlparser/v2 v2.5.16
 	github.com/willscott/go-nfs-client v0.0.0-20240104095149-b44639837b00
 	github.com/willscott/memphis v0.0.0-20210922141505-529d4987ab7e
-	go.opentelemetry.io/otel v1.27.0
-	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0
-	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0
-	go.opentelemetry.io/otel/metric v1.27.0
-	go.opentelemetry.io/otel/sdk v1.27.0
-	go.opentelemetry.io/otel/sdk/metric v1.27.0
-	go.opentelemetry.io/otel/trace v1.27.0
+	github.com/xuthus5/qbittorrent-client-go v0.0.0-20240710140754-51c95559ea0a
+	go.opentelemetry.io/otel v1.28.0
+	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0
+	go.opentelemetry.io/otel/exporters/prometheus v0.50.0
+	go.opentelemetry.io/otel/metric v1.28.0
+	go.opentelemetry.io/otel/sdk v1.28.0
+	go.opentelemetry.io/otel/sdk/metric v1.28.0
+	go.opentelemetry.io/otel/trace v1.28.0
 	golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
 	golang.org/x/net v0.26.0
 	golang.org/x/sync v0.7.0
-	golang.org/x/sys v0.21.0
+	golang.org/x/sys v0.22.0
 )
 
 require (
@@ -64,12 +67,13 @@ require (
 	github.com/agnivade/levenshtein v1.1.1 // indirect
 	github.com/ajwerner/btree v0.0.0-20211221152037-f427b3e689c0 // indirect
 	github.com/alecthomas/atomic v0.1.0-alpha2 // indirect
-	github.com/anacrolix/chansync v0.4.0 // indirect
+	github.com/anacrolix/chansync v0.4.1-0.20240627045151-1aa1ac392fe8 // indirect
 	github.com/anacrolix/envpprof v1.3.0 // indirect
 	github.com/anacrolix/generics v0.0.2-0.20240227122613-f95486179cab // indirect
 	github.com/anacrolix/go-libutp v1.3.1 // indirect
 	github.com/anacrolix/missinggo v1.3.0 // indirect
 	github.com/anacrolix/missinggo/perf v1.0.0 // indirect
+	github.com/anacrolix/missinggo/v2 v2.7.3 // indirect
 	github.com/anacrolix/mmsg v1.0.0 // indirect
 	github.com/anacrolix/multiless v0.3.1-0.20230203023154-f3d27407d8f1 // indirect
 	github.com/anacrolix/stm v0.5.0 // indirect
@@ -79,18 +83,20 @@ require (
 	github.com/andybalholm/brotli v1.1.0 // indirect
 	github.com/bahlo/generic-list-go v0.2.0 // indirect
 	github.com/benbjohnson/immutable v0.4.3 // indirect
+	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bits-and-blooms/bitset v1.13.0 // 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.11.6 // indirect
-	github.com/bytedance/sonic/loader v0.1.1 // indirect
+	github.com/bytedance/sonic v1.11.9 // indirect
+	github.com/bytedance/sonic/loader v0.2.0 // indirect
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
 	github.com/cespare/xxhash v1.1.0 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
 	github.com/cloudwego/base64x v0.1.4 // indirect
 	github.com/cloudwego/iasm v0.2.0 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
+	github.com/cyphar/filepath-securejoin v0.2.5 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/dgraph-io/ristretto v0.1.1 // indirect
 	github.com/edsrzf/mmap-go v1.1.0 // indirect
@@ -100,7 +106,7 @@ require (
 	github.com/gin-contrib/sse v0.1.0 // indirect
 	github.com/go-llsqlite/adapter v0.1.0 // indirect
 	github.com/go-llsqlite/crawshaw v0.5.2-0.20240425034140-f30eb7704568 // indirect
-	github.com/go-logr/logr v1.4.1 // indirect
+	github.com/go-logr/logr v1.4.2 // 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
@@ -115,15 +121,17 @@ require (
 	github.com/golang/snappy v0.0.4 // indirect
 	github.com/google/btree v1.1.2 // indirect
 	github.com/google/flatbuffers v24.3.25+incompatible // indirect
+	github.com/google/go-querystring v1.1.0 // indirect
+	github.com/gorilla/schema v1.4.1 // indirect
 	github.com/gorilla/websocket v1.5.1 // indirect
-	github.com/grafana/pyroscope-go/godeltaprof v0.1.7 // indirect
+	github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/hashicorp/go-multierror v1.1.1 // indirect
 	github.com/huandu/xstrings v1.4.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/compress v1.17.8 // indirect
-	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.8 // indirect
 	github.com/knadh/koanf/maps v0.1.1 // indirect
 	github.com/labstack/gommon v0.4.2 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
@@ -139,6 +147,7 @@ require (
 	github.com/mschoch/smat v0.2.0 // indirect
 	github.com/multiformats/go-multihash v0.2.3 // indirect
 	github.com/multiformats/go-varint v0.0.6 // indirect
+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/ncruces/go-strftime v0.1.9 // indirect
 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
 	github.com/pierrec/lz4/v4 v4.1.21 // indirect
@@ -163,10 +172,12 @@ require (
 	github.com/polydawn/go-timeless-api v0.0.0-20220821201550-b93919e12c56 // indirect
 	github.com/polydawn/refmt v0.89.0 // indirect
 	github.com/polydawn/rio v0.0.0-20220823181337-7c31ad9831a4 // indirect
+	github.com/prometheus/client_model v0.6.1 // indirect
+	github.com/prometheus/common v0.55.0 // indirect
+	github.com/prometheus/procfs v0.15.1 // indirect
 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 	github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
-	github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
 	github.com/samber/lo v1.39.0 // indirect
 	github.com/sosodev/duration v1.3.1 // indirect
 	github.com/spaolacci/murmur3 v1.1.0 // indirect
@@ -181,8 +192,8 @@ require (
 	go.etcd.io/bbolt v1.3.9 // indirect
 	go.opencensus.io v0.24.0 // indirect
 	go.opentelemetry.io/contrib v1.26.0 // indirect
-	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 // indirect
-	go.opentelemetry.io/proto/otlp v1.2.0 // indirect
+	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
+	go.opentelemetry.io/proto/otlp v1.3.1 // indirect
 	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
 	golang.org/x/arch v0.8.0 // indirect
 	golang.org/x/crypto v0.24.0 // indirect
@@ -190,10 +201,10 @@ require (
 	golang.org/x/text v0.16.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.22.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 // indirect
-	google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
 	google.golang.org/grpc v1.64.0 // indirect
-	google.golang.org/protobuf v1.34.1 // indirect
+	google.golang.org/protobuf v1.34.2 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	lukechampine.com/blake3 v1.1.6 // indirect
 	modernc.org/libc v1.50.5 // indirect
diff --git a/go.sum b/go.sum
index 6f42f02..a7f7ce9 100644
--- a/go.sum
+++ b/go.sum
@@ -50,8 +50,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/anacrolix/chansync v0.4.0 h1:Md0HM7zYCAO4KwNwgcIRgxNsMxiRuk7D1Ha0Uo+2y60=
-github.com/anacrolix/chansync v0.4.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
+github.com/anacrolix/chansync v0.4.1-0.20240627045151-1aa1ac392fe8 h1:eyb0bBaQKMOh5Se/Qg54shijc8K4zpQiOjEhKFADkQM=
+github.com/anacrolix/chansync v0.4.1-0.20240627045151-1aa1ac392fe8/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k=
 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=
@@ -68,8 +68,8 @@ github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgw
 github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU=
 github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68=
 github.com/anacrolix/log v0.14.2/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY=
-github.com/anacrolix/log v0.15.2 h1:LTSf5Wm6Q4GNWPFMBP7NPYV6UBVZzZLKckL+/Lj72Oo=
-github.com/anacrolix/log v0.15.2/go.mod h1:m0poRtlr41mriZlXBQ9SOVZ8yZBkLjOkDhd5Li5pITA=
+github.com/anacrolix/log v0.15.3-0.20240627045001-cd912c641d83 h1:9o/yVzzLzYaBDFx8B27yhkvBLhNnRAuSTK7Y+yZKVtU=
+github.com/anacrolix/log v0.15.3-0.20240627045001-cd912c641d83/go.mod h1:xvHjsYWWP7yO8PZwtuIp/k0DBlu07pSJqH4SEC78Vwc=
 github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM=
 github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM=
 github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s=
@@ -99,8 +99,8 @@ github.com/anacrolix/sync v0.5.1/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DC
 github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
 github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
 github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8=
-github.com/anacrolix/torrent v1.56.1 h1:QeJMOP0NuhpQ5dATsOqEL0vUO85aPMNMGP2FACNt0Eg=
-github.com/anacrolix/torrent v1.56.1/go.mod h1:5DMHbeIM1TuC5wTQ99XieKKLiYZYz6iB2lyZpKZEr6w=
+github.com/anacrolix/torrent v1.56.2-0.20240813010934-f4711825e84e h1:gfu86Ozd6rvq4mwSgy1s6SRlS8UeeCORKoqnXvlXtY0=
+github.com/anacrolix/torrent v1.56.2-0.20240813010934-f4711825e84e/go.mod h1:m6Jl1mdUG3wcapLuvn8ZwENi49DUCmiacom6plQ5rcI=
 github.com/anacrolix/upnp v0.1.4 h1:+2t2KA6QOhm/49zeNyeVwDu1ZYS9dB9wfxyVvh/wk7U=
 github.com/anacrolix/upnp v0.1.4/go.mod h1:Qyhbqo69gwNWvEk1xNTXsS5j7hMHef9hdr984+9fIic=
 github.com/anacrolix/utp v0.2.0 h1:65Cdmr6q9WSw2KsM+rtJFu7rqDzLl2bdysf4KlNPcFI=
@@ -119,6 +119,7 @@ github.com/benbjohnson/immutable v0.4.3 h1:GYHcksoJ9K6HyAUpGxwZURrbTkXA0Dh4otXGq
 github.com/benbjohnson/immutable v0.4.3/go.mod h1:qJIKKSmdqz1tVzNtst1DZzvaqOU1onk1rc03IeM3Owk=
 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=
@@ -135,10 +136,11 @@ github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2w
 github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
 github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 h1:GKTyiRCL6zVf5wWaqKnf+7Qs6GbEPfd4iMOitWzXJx8=
 github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8/go.mod h1:spo1JLcs67NmW1aVLEgtA8Yy1elc+X8y5SRW1sFW4Og=
-github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
-github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
-github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24=
+github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
 github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
+github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
 github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@@ -225,8 +227,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
 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.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
-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/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/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.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
@@ -293,10 +295,15 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/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.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/go-github/v63 v63.0.0 h1:13xwK/wk9alSokujB9lJkuzdmQuVn2QCPeck76wR3nE=
+github.com/google/go-github/v63 v63.0.0/go.mod h1:IqbcrgUmIcEaioWrGYei/09o+ge5vhffGOcxrO0AfmA=
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 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=
@@ -316,14 +323,16 @@ github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKp
 github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
+github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
 github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
 github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
 github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
 github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
-github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ=
-github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88=
-github.com/grafana/pyroscope-go/godeltaprof v0.1.7 h1:C11j63y7gymiW8VugJ9ZW0pWfxTZugdSJyC48olk5KY=
-github.com/grafana/pyroscope-go/godeltaprof v0.1.7/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE=
+github.com/grafana/pyroscope-go v1.1.2 h1:7vCfdORYQMCxIzI3NlYAs3FcBP760+gWuYWOyiVyYx8=
+github.com/grafana/pyroscope-go v1.1.2/go.mod h1:HSSmHo2KRn6FasBA4vK7BMiQqyQq8KSuBKvrhkXxYPU=
+github.com/grafana/pyroscope-go/godeltaprof v0.1.8 h1:iwOtYXeeVSAeYefJNaxDytgjKtUuKQbJqgAIjlnicKg=
+github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -345,8 +354,6 @@ github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq
 github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
 github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/iceber/iouring-go v0.0.0-20230403020409-002cfd2e2a90 h1:xrtfZokN++5kencK33hn2Kx3Uj8tGnjMEhdt6FMvHD0=
-github.com/iceber/iouring-go v0.0.0-20230403020409-002cfd2e2a90/go.mod h1:LEzdaZarZ5aqROlLIwJ4P7h3+4o71008fSy6wpaEB+s=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -359,13 +366,12 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
 github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
 github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
-github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
-github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
+github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
 github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
 github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
@@ -431,6 +437,8 @@ github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7B
 github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
 github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
 github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
 github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
@@ -515,19 +523,27 @@ 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.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
+github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
 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.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
 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.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
 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.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
 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=
@@ -637,6 +653,8 @@ github.com/willscott/memphis v0.0.0-20210922141505-529d4987ab7e h1:1eHCP4w7tMmpf
 github.com/willscott/memphis v0.0.0-20210922141505-529d4987ab7e/go.mod h1:59vHBW4EpjiL5oiqgCrBp1Tc9JXRzKCNMEOaGmNfSHo=
 github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
 github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
+github.com/xuthus5/qbittorrent-client-go v0.0.0-20240710140754-51c95559ea0a h1:/3NF320wvXk5nm9Ng02eKTiWSYf20r4acufqecGLpfo=
+github.com/xuthus5/qbittorrent-client-go v0.0.0-20240710140754-51c95559ea0a/go.mod h1:lP2yxMU6WGTmHqI9T+SrEw3wo7k5kUyiA9FBOK9NKMQ=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@@ -654,32 +672,33 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
 go.opentelemetry.io/contrib v1.26.0 h1:8/CmxLl5uDm37V9rdqbZcVLvYigAE2vMostBq3nGDrI=
 go.opentelemetry.io/contrib v1.26.0/go.mod h1:Tmhw9grdWtmXy6DxZNpIAudzYJqLeEM2P6QTZQSRwU8=
 go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
-go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
-go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
-go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0 h1:CIHWikMsN3wO+wq1Tp5VGdVRTcON+DmOJSfDjXypKOc=
-go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.27.0/go.mod h1:TNupZ6cxqyFEpLXAZW7On+mLFL0/g0TE3unIYL91xWc=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0 h1:1wp/gyxsuYtuE/JFxsQRtcCDtMrO2qMvlfXALU5wkzI=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.26.0/go.mod h1:gbTHmghkGgqxMomVQQMur1Nba4M0MQ8AYThXDUjsJ38=
+go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
+go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk=
+go.opentelemetry.io/otel/exporters/prometheus v0.50.0 h1:2Ewsda6hejmbhGFyUvWZjUThC98Cf8Zy6g0zkIimOng=
+go.opentelemetry.io/otel/exporters/prometheus v0.50.0/go.mod h1:pMm5PkUo5YwbLiuEf7t2xg4wbP0/eSJrMxIMxKosynY=
 go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
-go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
-go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
+go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
+go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
 go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
-go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
-go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
-go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI=
-go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw=
+go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
+go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
+go.opentelemetry.io/otel/sdk/metric v1.28.0 h1:OkuaKgKrgAbYrrY0t92c+cC+2F6hsFNnCQArXCKlg08=
+go.opentelemetry.io/otel/sdk/metric v1.28.0/go.mod h1:cWPjykihLAPvXKi4iZc1dpER3Jdq2Z0YLse3moQUCpg=
 go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
-go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
-go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
-go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
-go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
+go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
+go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
+go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
+go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
 go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
-golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
 golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -804,7 +823,6 @@ golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -826,8 +844,8 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
-golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -921,10 +939,10 @@ 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/googleapis/api v0.0.0-20240520151616-dc85e6b867a5 h1:P8OJ/WCl/Xo4E4zoe4/bifHpSmmKwARqyqE4nW6J2GQ=
-google.golang.org/genproto/googleapis/api v0.0.0-20240520151616-dc85e6b867a5/go.mod h1:RGnPtTG7r4i8sPlNyDeikXF99hMM+hN6QMm4ooG9g2g=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291 h1:AgADTJarZTBqgjiUzRgfaBchgYB3/WFTC80GPwsMcRI=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240515191416-fc5f0ca64291/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
 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=
@@ -946,8 +964,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
-google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
 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=
@@ -1001,7 +1019,6 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
 modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
 nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 zombiezen.com/go/sqlite v1.3.0 h1:98g1gnCm+CNz6AuQHu0gqyw7gR2WU3O3PJufDOStpUs=
diff --git a/pkg/ctxbilly/uring.go b/pkg/ctxbilly/uring.go
index 3d54ae6..fad7318 100644
--- a/pkg/ctxbilly/uring.go
+++ b/pkg/ctxbilly/uring.go
@@ -1,355 +1,355 @@
 package ctxbilly
 
-import (
-	"context"
-	"errors"
-	"fmt"
-	"os"
-	"path/filepath"
-	"strings"
+// import (
+// 	"context"
+// 	"errors"
+// 	"fmt"
+// 	"os"
+// 	"path/filepath"
+// 	"strings"
 
-	securejoin "github.com/cyphar/filepath-securejoin"
-	"github.com/iceber/iouring-go"
-)
+// 	securejoin "github.com/cyphar/filepath-securejoin"
+// 	"github.com/iceber/iouring-go"
+// )
 
-func NewURingFS() (*UringFS, error) {
-	ur, err := iouring.New(64, iouring.WithAsync())
-	if err != nil {
-		return nil, err
-	}
-
-	return &UringFS{
-		ur: ur,
-	}, nil
-}
-
-var _ Filesystem = (*UringFS)(nil)
-
-const (
-	defaultDirectoryMode = 0o755
-	defaultCreateMode    = 0o666
-)
-
-// UringFS is a fs implementation based on the OS filesystem which is bound to
-// a base dir.
-// Prefer this fs implementation over ChrootOS.
-//
-// Behaviours of note:
-//  1. Read and write operations can only be directed to files which descends
-//     from the base dir.
-//  2. Symlinks don't have their targets modified, and therefore can point
-//     to locations outside the base dir or to non-existent paths.
-//  3. Readlink and Lstat ensures that the link file is located within the base
-//     dir, evaluating any symlinks that file or base dir may contain.
-type UringFS struct {
-	ur      *iouring.IOURing
-	baseDir string
-}
-
-func newBoundOS(d string) *UringFS {
-	return &UringFS{baseDir: d}
-}
-
-func (fs *UringFS) Create(ctx context.Context, filename string) (File, error) {
-	return fs.OpenFile(ctx, filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode)
-}
-
-func (fs *UringFS) OpenFile(ctx context.Context, filename string, flag int, perm os.FileMode) (File, error) {
-	fn, err := fs.abs(filename)
-	if err != nil {
-		return nil, err
-	}
-
-	f, err := os.OpenFile(fn, flag, perm)
-	if err != nil {
-		return nil, err
-	}
-
-	return newFile(fs.ur, f)
-}
-
-func (fs *UringFS) ReadDir(ctx context.Context, path string) ([]os.FileInfo, error) {
-	dir, err := fs.abs(path)
-	if err != nil {
-		return nil, err
-	}
-
-	entries, err := os.ReadDir(dir)
-	if err != nil {
-		return nil, err
-	}
-	infos := make([]os.FileInfo, 0, len(entries))
-	for _, v := range entries {
-		info, err := v.Info()
-		if err != nil {
-			return nil, err
-		}
-
-		infos = append(infos, info)
-	}
-
-	return infos, nil
-}
-
-func (fs *UringFS) Rename(ctx context.Context, from, to string) error {
-	f, err := fs.abs(from)
-	if err != nil {
-		return err
-	}
-	t, err := fs.abs(to)
-	if err != nil {
-		return err
-	}
-
-	// MkdirAll for target name.
-	if err := fs.createDir(t); err != nil {
-		return err
-	}
-
-	return os.Rename(f, t)
-}
-
-func (fs *UringFS) MkdirAll(ctx context.Context, path string, perm os.FileMode) error {
-	dir, err := fs.abs(path)
-	if err != nil {
-		return err
-	}
-	return os.MkdirAll(dir, perm)
-}
-
-func (fs *UringFS) Stat(ctx context.Context, filename string) (os.FileInfo, error) {
-	filename, err := fs.abs(filename)
-	if err != nil {
-		return nil, err
-	}
-	return os.Stat(filename)
-}
-
-func (fs *UringFS) Remove(ctx context.Context, filename string) error {
-	fn, err := fs.abs(filename)
-	if err != nil {
-		return err
-	}
-	return os.Remove(fn)
-}
-
-func (fs *UringFS) Join(elem ...string) string {
-	return filepath.Join(elem...)
-}
-
-func (fs *UringFS) RemoveAll(path string) error {
-	dir, err := fs.abs(path)
-	if err != nil {
-		return err
-	}
-	return os.RemoveAll(dir)
-}
-
-func (fs *UringFS) Symlink(ctx context.Context, target, link string) error {
-	ln, err := fs.abs(link)
-	if err != nil {
-		return err
-	}
-	// MkdirAll for containing dir.
-	if err := fs.createDir(ln); err != nil {
-		return err
-	}
-	return os.Symlink(target, ln)
-}
-
-func (fs *UringFS) Lstat(ctx context.Context, filename string) (os.FileInfo, error) {
-	filename = filepath.Clean(filename)
-	if !filepath.IsAbs(filename) {
-		filename = filepath.Join(fs.baseDir, filename)
-	}
-	if ok, err := fs.insideBaseDirEval(filename); !ok {
-		return nil, err
-	}
-	return os.Lstat(filename)
-}
-
-func (fs *UringFS) Readlink(ctx context.Context, link string) (string, error) {
-	if !filepath.IsAbs(link) {
-		link = filepath.Clean(filepath.Join(fs.baseDir, link))
-	}
-	if ok, err := fs.insideBaseDirEval(link); !ok {
-		return "", err
-	}
-	return os.Readlink(link)
-}
-
-// Chroot returns a new OS filesystem, with the base dir set to the
-// result of joining the provided path with the underlying base dir.
-// func (fs *UringFS) Chroot(path string) (Filesystem, error) {
-// 	joined, err := securejoin.SecureJoin(fs.baseDir, path)
+// func NewURingFS() (*UringFS, error) {
+// 	ur, err := iouring.New(64, iouring.WithAsync())
 // 	if err != nil {
 // 		return nil, err
 // 	}
-// 	return newBoundOS(joined), nil
+
+// 	return &UringFS{
+// 		ur: ur,
+// 	}, nil
 // }
 
-// Root returns the current base dir of the billy.Filesystem.
-// This is required in order for this implementation to be a drop-in
-// replacement for other upstream implementations (e.g. memory and osfs).
-func (fs *UringFS) Root() string {
-	return fs.baseDir
-}
+// var _ Filesystem = (*UringFS)(nil)
 
-func (fs *UringFS) createDir(fullpath string) error {
-	dir := filepath.Dir(fullpath)
-	if dir != "." {
-		if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil {
-			return err
-		}
-	}
+// const (
+// 	defaultDirectoryMode = 0o755
+// 	defaultCreateMode    = 0o666
+// )
 
-	return nil
-}
+// // UringFS is a fs implementation based on the OS filesystem which is bound to
+// // a base dir.
+// // Prefer this fs implementation over ChrootOS.
+// //
+// // Behaviours of note:
+// //  1. Read and write operations can only be directed to files which descends
+// //     from the base dir.
+// //  2. Symlinks don't have their targets modified, and therefore can point
+// //     to locations outside the base dir or to non-existent paths.
+// //  3. Readlink and Lstat ensures that the link file is located within the base
+// //     dir, evaluating any symlinks that file or base dir may contain.
+// type UringFS struct {
+// 	ur      *iouring.IOURing
+// 	baseDir string
+// }
 
-// abs transforms filename to an absolute path, taking into account the base dir.
-// Relative paths won't be allowed to ascend the base dir, so `../file` will become
-// `/working-dir/file`.
-//
-// Note that if filename is a symlink, the returned address will be the target of the
-// symlink.
-func (fs *UringFS) abs(filename string) (string, error) {
-	if filename == fs.baseDir {
-		filename = string(filepath.Separator)
-	}
+// func newBoundOS(d string) *UringFS {
+// 	return &UringFS{baseDir: d}
+// }
 
-	path, err := securejoin.SecureJoin(fs.baseDir, filename)
-	if err != nil {
-		return "", nil
-	}
+// func (fs *UringFS) Create(ctx context.Context, filename string) (File, error) {
+// 	return fs.OpenFile(ctx, filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode)
+// }
 
-	return path, nil
-}
+// func (fs *UringFS) OpenFile(ctx context.Context, filename string, flag int, perm os.FileMode) (File, error) {
+// 	fn, err := fs.abs(filename)
+// 	if err != nil {
+// 		return nil, err
+// 	}
 
-// insideBaseDirEval checks whether filename is contained within
-// a dir that is within the fs.baseDir, by first evaluating any symlinks
-// that either filename or fs.baseDir may contain.
-func (fs *UringFS) insideBaseDirEval(filename string) (bool, error) {
-	dir, err := filepath.EvalSymlinks(filepath.Dir(filename))
-	if dir == "" || os.IsNotExist(err) {
-		dir = filepath.Dir(filename)
-	}
-	wd, err := filepath.EvalSymlinks(fs.baseDir)
-	if wd == "" || os.IsNotExist(err) {
-		wd = fs.baseDir
-	}
-	if filename != wd && dir != wd && !strings.HasPrefix(dir, wd+string(filepath.Separator)) {
-		return false, fmt.Errorf("path outside base dir")
-	}
-	return true, nil
-}
+// 	f, err := os.OpenFile(fn, flag, perm)
+// 	if err != nil {
+// 		return nil, err
+// 	}
 
-func newFile(fsur *iouring.IOURing, f *os.File) (*URingFile, error) {
-	ur, err := iouring.New(64, iouring.WithAttachWQ(fsur))
-	if err != nil {
-		return nil, err
-	}
+// 	return newFile(fs.ur, f)
+// }
 
-	return &URingFile{
-		ur: ur,
-		f:  f,
-	}, nil
-}
+// func (fs *UringFS) ReadDir(ctx context.Context, path string) ([]os.FileInfo, error) {
+// 	dir, err := fs.abs(path)
+// 	if err != nil {
+// 		return nil, err
+// 	}
 
-type URingFile struct {
-	ur *iouring.IOURing
-	f  *os.File
-}
+// 	entries, err := os.ReadDir(dir)
+// 	if err != nil {
+// 		return nil, err
+// 	}
+// 	infos := make([]os.FileInfo, 0, len(entries))
+// 	for _, v := range entries {
+// 		info, err := v.Info()
+// 		if err != nil {
+// 			return nil, err
+// 		}
 
-// Close implements File.
-func (o *URingFile) Close(ctx context.Context) error {
-	return errors.Join(o.ur.UnregisterFile(o.f), o.Close(ctx))
-}
+// 		infos = append(infos, info)
+// 	}
 
-// Name implements File.
-func (o *URingFile) Name() string {
-	return o.f.Name()
-}
+// 	return infos, nil
+// }
 
-// Read implements File.
-func (o *URingFile) Read(ctx context.Context, p []byte) (n int, err error) {
-	req, err := o.ur.Read(o.f, p, nil)
-	if err != nil {
-		return 0, err
-	}
-	defer req.Cancel()
+// func (fs *UringFS) Rename(ctx context.Context, from, to string) error {
+// 	f, err := fs.abs(from)
+// 	if err != nil {
+// 		return err
+// 	}
+// 	t, err := fs.abs(to)
+// 	if err != nil {
+// 		return err
+// 	}
 
-	select {
-	case <-req.Done():
-		return req.GetRes()
-	case <-ctx.Done():
-		req.Cancel()
-		<-req.Done()
-		return req.GetRes()
-	}
-}
+// 	// MkdirAll for target name.
+// 	if err := fs.createDir(t); err != nil {
+// 		return err
+// 	}
 
-// ReadAt implements File.
-func (o *URingFile) ReadAt(ctx context.Context, p []byte, off int64) (n int, err error) {
-	req, err := o.ur.Pread(o.f, p, uint64(off), nil)
-	if err != nil {
-		return 0, err
-	}
-	defer req.Cancel()
+// 	return os.Rename(f, t)
+// }
 
-	select {
-	case <-req.Done():
-		return req.GetRes()
-	case <-ctx.Done():
-		req.Cancel()
-		<-req.Done()
-		return req.GetRes()
-	}
-}
+// func (fs *UringFS) MkdirAll(ctx context.Context, path string, perm os.FileMode) error {
+// 	dir, err := fs.abs(path)
+// 	if err != nil {
+// 		return err
+// 	}
+// 	return os.MkdirAll(dir, perm)
+// }
 
-// Write implements File.
-func (o *URingFile) Write(ctx context.Context, p []byte) (n int, err error) {
-	req, err := o.ur.Write(o.f, p, nil)
-	if err != nil {
-		return 0, err
-	}
-	defer req.Cancel()
+// func (fs *UringFS) Stat(ctx context.Context, filename string) (os.FileInfo, error) {
+// 	filename, err := fs.abs(filename)
+// 	if err != nil {
+// 		return nil, err
+// 	}
+// 	return os.Stat(filename)
+// }
 
-	select {
-	case <-req.Done():
-		return req.GetRes()
-	case <-ctx.Done():
-		req.Cancel()
-		<-req.Done()
-		return req.GetRes()
-	}
-}
+// func (fs *UringFS) Remove(ctx context.Context, filename string) error {
+// 	fn, err := fs.abs(filename)
+// 	if err != nil {
+// 		return err
+// 	}
+// 	return os.Remove(fn)
+// }
 
-// WriteAt implements File.
-func (o *URingFile) WriteAt(ctx context.Context, p []byte, off int64) (n int, err error) {
-	req, err := o.ur.Pwrite(o.f, p, uint64(off), nil)
-	if err != nil {
-		return 0, err
-	}
-	defer req.Cancel()
+// func (fs *UringFS) Join(elem ...string) string {
+// 	return filepath.Join(elem...)
+// }
 
-	select {
-	case <-req.Done():
-		return req.GetRes()
-	case <-ctx.Done():
-		req.Cancel()
-		<-req.Done()
-		return req.GetRes()
-	}
-}
+// func (fs *UringFS) RemoveAll(path string) error {
+// 	dir, err := fs.abs(path)
+// 	if err != nil {
+// 		return err
+// 	}
+// 	return os.RemoveAll(dir)
+// }
 
-// Seek implements File.
-func (o *URingFile) Seek(offset int64, whence int) (int64, error) {
-	return o.f.Seek(offset, whence)
-}
+// func (fs *UringFS) Symlink(ctx context.Context, target, link string) error {
+// 	ln, err := fs.abs(link)
+// 	if err != nil {
+// 		return err
+// 	}
+// 	// MkdirAll for containing dir.
+// 	if err := fs.createDir(ln); err != nil {
+// 		return err
+// 	}
+// 	return os.Symlink(target, ln)
+// }
 
-// Truncate implements File.
-func (o *URingFile) Truncate(ctx context.Context, size int64) error {
-	return o.f.Truncate(size)
-}
+// func (fs *UringFS) Lstat(ctx context.Context, filename string) (os.FileInfo, error) {
+// 	filename = filepath.Clean(filename)
+// 	if !filepath.IsAbs(filename) {
+// 		filename = filepath.Join(fs.baseDir, filename)
+// 	}
+// 	if ok, err := fs.insideBaseDirEval(filename); !ok {
+// 		return nil, err
+// 	}
+// 	return os.Lstat(filename)
+// }
 
-var _ File = (*URingFile)(nil)
+// func (fs *UringFS) Readlink(ctx context.Context, link string) (string, error) {
+// 	if !filepath.IsAbs(link) {
+// 		link = filepath.Clean(filepath.Join(fs.baseDir, link))
+// 	}
+// 	if ok, err := fs.insideBaseDirEval(link); !ok {
+// 		return "", err
+// 	}
+// 	return os.Readlink(link)
+// }
+
+// // Chroot returns a new OS filesystem, with the base dir set to the
+// // result of joining the provided path with the underlying base dir.
+// // func (fs *UringFS) Chroot(path string) (Filesystem, error) {
+// // 	joined, err := securejoin.SecureJoin(fs.baseDir, path)
+// // 	if err != nil {
+// // 		return nil, err
+// // 	}
+// // 	return newBoundOS(joined), nil
+// // }
+
+// // Root returns the current base dir of the billy.Filesystem.
+// // This is required in order for this implementation to be a drop-in
+// // replacement for other upstream implementations (e.g. memory and osfs).
+// func (fs *UringFS) Root() string {
+// 	return fs.baseDir
+// }
+
+// func (fs *UringFS) createDir(fullpath string) error {
+// 	dir := filepath.Dir(fullpath)
+// 	if dir != "." {
+// 		if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil {
+// 			return err
+// 		}
+// 	}
+
+// 	return nil
+// }
+
+// // abs transforms filename to an absolute path, taking into account the base dir.
+// // Relative paths won't be allowed to ascend the base dir, so `../file` will become
+// // `/working-dir/file`.
+// //
+// // Note that if filename is a symlink, the returned address will be the target of the
+// // symlink.
+// func (fs *UringFS) abs(filename string) (string, error) {
+// 	if filename == fs.baseDir {
+// 		filename = string(filepath.Separator)
+// 	}
+
+// 	path, err := securejoin.SecureJoin(fs.baseDir, filename)
+// 	if err != nil {
+// 		return "", nil
+// 	}
+
+// 	return path, nil
+// }
+
+// // insideBaseDirEval checks whether filename is contained within
+// // a dir that is within the fs.baseDir, by first evaluating any symlinks
+// // that either filename or fs.baseDir may contain.
+// func (fs *UringFS) insideBaseDirEval(filename string) (bool, error) {
+// 	dir, err := filepath.EvalSymlinks(filepath.Dir(filename))
+// 	if dir == "" || os.IsNotExist(err) {
+// 		dir = filepath.Dir(filename)
+// 	}
+// 	wd, err := filepath.EvalSymlinks(fs.baseDir)
+// 	if wd == "" || os.IsNotExist(err) {
+// 		wd = fs.baseDir
+// 	}
+// 	if filename != wd && dir != wd && !strings.HasPrefix(dir, wd+string(filepath.Separator)) {
+// 		return false, fmt.Errorf("path outside base dir")
+// 	}
+// 	return true, nil
+// }
+
+// func newFile(fsur *iouring.IOURing, f *os.File) (*URingFile, error) {
+// 	ur, err := iouring.New(64, iouring.WithAttachWQ(fsur))
+// 	if err != nil {
+// 		return nil, err
+// 	}
+
+// 	return &URingFile{
+// 		ur: ur,
+// 		f:  f,
+// 	}, nil
+// }
+
+// type URingFile struct {
+// 	ur *iouring.IOURing
+// 	f  *os.File
+// }
+
+// // Close implements File.
+// func (o *URingFile) Close(ctx context.Context) error {
+// 	return errors.Join(o.ur.UnregisterFile(o.f), o.Close(ctx))
+// }
+
+// // Name implements File.
+// func (o *URingFile) Name() string {
+// 	return o.f.Name()
+// }
+
+// // Read implements File.
+// func (o *URingFile) Read(ctx context.Context, p []byte) (n int, err error) {
+// 	req, err := o.ur.Read(o.f, p, nil)
+// 	if err != nil {
+// 		return 0, err
+// 	}
+// 	defer req.Cancel()
+
+// 	select {
+// 	case <-req.Done():
+// 		return req.GetRes()
+// 	case <-ctx.Done():
+// 		req.Cancel()
+// 		<-req.Done()
+// 		return req.GetRes()
+// 	}
+// }
+
+// // ReadAt implements File.
+// func (o *URingFile) ReadAt(ctx context.Context, p []byte, off int64) (n int, err error) {
+// 	req, err := o.ur.Pread(o.f, p, uint64(off), nil)
+// 	if err != nil {
+// 		return 0, err
+// 	}
+// 	defer req.Cancel()
+
+// 	select {
+// 	case <-req.Done():
+// 		return req.GetRes()
+// 	case <-ctx.Done():
+// 		req.Cancel()
+// 		<-req.Done()
+// 		return req.GetRes()
+// 	}
+// }
+
+// // Write implements File.
+// func (o *URingFile) Write(ctx context.Context, p []byte) (n int, err error) {
+// 	req, err := o.ur.Write(o.f, p, nil)
+// 	if err != nil {
+// 		return 0, err
+// 	}
+// 	defer req.Cancel()
+
+// 	select {
+// 	case <-req.Done():
+// 		return req.GetRes()
+// 	case <-ctx.Done():
+// 		req.Cancel()
+// 		<-req.Done()
+// 		return req.GetRes()
+// 	}
+// }
+
+// // WriteAt implements File.
+// func (o *URingFile) WriteAt(ctx context.Context, p []byte, off int64) (n int, err error) {
+// 	req, err := o.ur.Pwrite(o.f, p, uint64(off), nil)
+// 	if err != nil {
+// 		return 0, err
+// 	}
+// 	defer req.Cancel()
+
+// 	select {
+// 	case <-req.Done():
+// 		return req.GetRes()
+// 	case <-ctx.Done():
+// 		req.Cancel()
+// 		<-req.Done()
+// 		return req.GetRes()
+// 	}
+// }
+
+// // Seek implements File.
+// func (o *URingFile) Seek(offset int64, whence int) (int64, error) {
+// 	return o.f.Seek(offset, whence)
+// }
+
+// // Truncate implements File.
+// func (o *URingFile) Truncate(ctx context.Context, size int64) error {
+// 	return o.f.Truncate(size)
+// }
+
+// var _ File = (*URingFile)(nil)
diff --git a/src/delivery/http.go b/src/delivery/http.go
index c8f4f76..1dea77e 100644
--- a/src/delivery/http.go
+++ b/src/delivery/http.go
@@ -9,13 +9,13 @@ import (
 	"git.kmsign.ru/royalcat/tstor/src/config"
 	"git.kmsign.ru/royalcat/tstor/src/sources/torrent"
 	"git.kmsign.ru/royalcat/tstor/src/vfs"
-	"github.com/anacrolix/missinggo/v2/filecache"
 	echopprof "github.com/labstack/echo-contrib/pprof"
 	"github.com/labstack/echo/v4"
 	"github.com/labstack/echo/v4/middleware"
+	"github.com/prometheus/client_golang/prometheus/promhttp"
 )
 
-func New(fc *filecache.Cache, s *torrent.Daemon, vfs vfs.Filesystem, logPath string, cfg *config.Settings) error {
+func Run(s *torrent.Daemon, vfs vfs.Filesystem, logPath string, cfg *config.Settings) error {
 	log := slog.With()
 
 	r := echo.New()
@@ -29,12 +29,11 @@ func New(fc *filecache.Cache, s *torrent.Daemon, vfs vfs.Filesystem, logPath str
 	echopprof.Register(r)
 
 	r.Any("/graphql", echo.WrapHandler((GraphQLHandler(s, vfs))))
+	r.GET("/metrics", echo.WrapHandler(promhttp.Handler()))
 
 	log.Info("starting webserver", "host", fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port))
 
-	go r.Start((fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)))
-
-	return nil
+	return r.Start((fmt.Sprintf("%s:%d", cfg.WebUi.IP, cfg.WebUi.Port)))
 }
 
 func Logger() echo.MiddlewareFunc {
diff --git a/src/sources/qbittorrent/client.go b/src/sources/qbittorrent/client.go
new file mode 100644
index 0000000..5fb2810
--- /dev/null
+++ b/src/sources/qbittorrent/client.go
@@ -0,0 +1,85 @@
+package qbittorrent
+
+import (
+	"context"
+	"fmt"
+	"slices"
+	"time"
+
+	"github.com/xuthus5/qbittorrent-client-go/qbittorrent"
+)
+
+type client struct {
+	qb qbittorrent.Client
+}
+
+func wrapClient(qb qbittorrent.Client) *client {
+	return &client{qb: qb}
+}
+
+func (f *client) getFileContent(ctx context.Context, hash string, contextIndex int) (*qbittorrent.TorrentContent, error) {
+	contents, err := f.qb.Torrent().GetContents(hash)
+	if err != nil {
+		return nil, err
+	}
+
+	contentIndex := slices.IndexFunc(contents, func(c *qbittorrent.TorrentContent) bool {
+		return c.Index == contextIndex
+	})
+	if contentIndex == -1 {
+		return nil, fmt.Errorf("content not found")
+	}
+
+	return contents[contentIndex], nil
+}
+
+func (f *client) isPieceComplete(ctx context.Context, hash string, pieceIndex int) (bool, error) {
+	completion, err := f.qb.Torrent().GetPiecesStates(hash)
+	if err != nil {
+		return false, err
+	}
+
+	if completion[pieceIndex] == 2 {
+		return true, nil
+	}
+
+	return false, nil
+}
+
+func (f *client) waitPieceToComplete(ctx context.Context, hash string, pieceIndex int) error {
+	const checkingInterval = 1 * time.Second
+
+	ok, err := f.isPieceComplete(ctx, hash, pieceIndex)
+	if err != nil {
+		return err
+	}
+	if ok {
+		return nil
+	}
+
+	if deadline, ok := ctx.Deadline(); ok && time.Until(deadline) < checkingInterval {
+		return context.DeadlineExceeded
+	}
+
+	ticker := time.NewTicker(checkingInterval)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ctx.Done():
+			return ctx.Err()
+		case <-ticker.C:
+			ok, err := f.isPieceComplete(ctx, hash, pieceIndex)
+			if err != nil {
+				return err
+			}
+			if ok {
+				return nil
+			}
+
+			if deadline, ok := ctx.Deadline(); ok && time.Until(deadline) < checkingInterval {
+				return context.DeadlineExceeded
+			}
+		}
+	}
+}
diff --git a/src/sources/qbittorrent/daemon.go b/src/sources/qbittorrent/daemon.go
new file mode 100644
index 0000000..abb19ae
--- /dev/null
+++ b/src/sources/qbittorrent/daemon.go
@@ -0,0 +1,101 @@
+package qbittorrent
+
+import (
+	"bytes"
+	"context"
+	"path"
+
+	"git.kmsign.ru/royalcat/tstor/src/vfs"
+	"github.com/anacrolix/torrent/metainfo"
+	"github.com/anacrolix/torrent/types/infohash"
+	"github.com/royalcat/ctxio"
+	"github.com/xuthus5/qbittorrent-client-go/qbittorrent"
+)
+
+type Daemon struct {
+	qb      qbittorrent.Client
+	client  *client
+	dataDir string
+}
+
+func NewDaemon(dir string) (*Daemon, error) {
+
+	dataDir := dir + "/data"
+	qb, err := qbittorrent.NewClient(&qbittorrent.Config{
+		Address: "localhost:8080",
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return &Daemon{
+		qb:      qb,
+		dataDir: dataDir,
+		client:  wrapClient(qb),
+	}, nil
+}
+
+func (fs *Daemon) torrentPath(ih infohash.T) string {
+	return path.Join(fs.dataDir, ih.HexString())
+}
+
+func (fs *Daemon) addTorrent(ctx context.Context, f vfs.File) error {
+	file, err := ctxio.ReadAll(ctx, f)
+	if err != nil {
+		return err
+	}
+
+	mi, err := metainfo.Load(bytes.NewBuffer(file))
+	if err != nil {
+		return err
+	}
+	ih := mi.HashInfoBytes()
+
+	err = fs.qb.Torrent().AddNewTorrent(&qbittorrent.TorrentAddOption{
+		Torrents: []*qbittorrent.TorrentAddFileMetadata{
+			{
+				Data: file,
+			},
+		},
+		SavePath: fs.torrentPath(ih),
+		// SequentialDownload: "true",
+		// FirstLastPiecePrio: "true",
+	})
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (fs *Daemon) TorrentFS(ctx context.Context, file vfs.File) (*FS, error) {
+	ih, err := readInfoHash(ctx, file)
+	if err != nil {
+		return nil, err
+	}
+
+	existing, err := fs.qb.Torrent().GetTorrents(&qbittorrent.TorrentOption{
+		Hashes: []string{ih.HexString()},
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	if len(existing) == 0 {
+		err := fs.addTorrent(ctx, file)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return newTorrentFS(fs.client, file.Name(), ih.HexString(), fs.torrentPath(ih))
+}
+
+// TODO caching
+func readInfoHash(ctx context.Context, file vfs.File) (infohash.T, error) {
+	mi, err := metainfo.Load(ctxio.IoReader(ctx, file))
+	if err != nil {
+		return infohash.T{}, err
+	}
+	return mi.HashInfoBytes(), nil
+}
diff --git a/src/sources/qbittorrent/fs.go b/src/sources/qbittorrent/fs.go
new file mode 100644
index 0000000..30acf06
--- /dev/null
+++ b/src/sources/qbittorrent/fs.go
@@ -0,0 +1,228 @@
+package qbittorrent
+
+import (
+	"context"
+	"io"
+	"io/fs"
+	"os"
+	"path"
+	"time"
+
+	"git.kmsign.ru/royalcat/tstor/src/vfs"
+)
+
+type FS struct {
+	client  *client
+	name    string
+	hash    string
+	dataDir string
+}
+
+var _ vfs.Filesystem = (*FS)(nil)
+
+func newTorrentFS(client *client, name string, hash string, dataDir string) (*FS, error) {
+	return &FS{
+		client:  client,
+		name:    name,
+		hash:    hash,
+		dataDir: dataDir,
+	}, nil
+}
+
+// Info implements vfs.Filesystem.
+func (f *FS) Info() (fs.FileInfo, error) {
+	return vfs.NewDirInfo(f.name), nil
+}
+
+// IsDir implements vfs.Filesystem.
+func (f *FS) IsDir() bool {
+	return true
+}
+
+// Name implements vfs.Filesystem.
+func (f *FS) Name() string {
+	return path.Base(f.dataDir)
+}
+
+// Open implements vfs.Filesystem.
+func (f *FS) Open(ctx context.Context, filename string) (vfs.File, error) {
+	panic("unimplemented")
+}
+
+// ReadDir implements vfs.Filesystem.
+func (f *FS) ReadDir(ctx context.Context, path string) ([]fs.DirEntry, error) {
+	panic("unimplemented")
+}
+
+// Stat implements vfs.Filesystem.
+func (f *FS) Stat(ctx context.Context, filename string) (fs.FileInfo, error) {
+	return vfs.NewDirInfo(f.name), nil
+}
+
+// Type implements vfs.Filesystem.
+func (f *FS) Type() fs.FileMode {
+	return vfs.ROMode
+}
+
+// Unlink implements vfs.Filesystem.
+func (f *FS) Unlink(ctx context.Context, filename string) error {
+	panic("unimplemented")
+}
+
+func openFile(ctx context.Context, client client, hash, filePath string) *File {
+	client.getFileContent(ctx, hash, 0)
+
+	return &File{
+		client:   client,
+		hash:     hash,
+		filePath: filePath,
+	}
+}
+
+type File struct {
+	client       client
+	hash         string
+	dataDir      string
+	filePath     string // path inside a torrent directory
+	contentIndex int
+	pieceSize    int
+	fileSize     int64
+
+	offset int64
+	osfile *os.File
+}
+
+var _ vfs.File = (*File)(nil)
+
+// Close implements vfs.File.
+func (f *File) Close(ctx context.Context) error {
+	if f.osfile != nil {
+		err := f.osfile.Close()
+		f.osfile = nil
+		return err
+	}
+	return nil
+}
+
+// Info implements vfs.File.
+func (f *File) Info() (fs.FileInfo, error) {
+	return &fileInfo{name: path.Base(f.filePath), size: f.fileSize}, nil
+}
+
+// IsDir implements vfs.File.
+func (f *File) IsDir() bool {
+	return false
+}
+
+// Seek implements vfs.File.
+func (f *File) Seek(offset int64, whence int) (int64, error) {
+	switch whence {
+	case io.SeekStart:
+		f.offset = offset
+	case io.SeekCurrent:
+		f.offset += offset
+	case io.SeekEnd:
+		f.offset = f.fileSize + offset
+	}
+	return f.offset, nil
+}
+
+// Name implements vfs.File.
+func (f *File) Name() string {
+	return path.Base(f.filePath)
+}
+
+// Read implements vfs.File.
+func (f *File) Read(ctx context.Context, p []byte) (n int, err error) {
+	pieceIndex := int(f.offset / int64(f.pieceSize))
+	err = f.client.waitPieceToComplete(ctx, f.hash, pieceIndex)
+	if err != nil {
+		return 0, err
+	}
+
+	descriptor, err := f.descriptor()
+	if err != nil {
+		return 0, err
+	}
+
+	n, err = descriptor.ReadAt(p, f.offset)
+	f.offset += int64(n)
+	return n, err
+}
+
+// ReadAt implements vfs.File.
+func (f *File) ReadAt(ctx context.Context, p []byte, off int64) (n int, err error) {
+	pieceIndex := int(off / int64(f.pieceSize))
+	err = f.client.waitPieceToComplete(ctx, f.hash, pieceIndex)
+	if err != nil {
+		return 0, err
+	}
+
+	descriptor, err := f.descriptor()
+	if err != nil {
+		return 0, err
+	}
+
+	return descriptor.ReadAt(p, off)
+}
+
+// Size implements vfs.File.
+func (f *File) Size() int64 {
+	return f.fileSize
+}
+
+// Type implements vfs.File.
+func (f *File) Type() fs.FileMode {
+	return vfs.ROMode
+}
+
+func (f *File) descriptor() (*os.File, error) {
+	if f.osfile != nil {
+		return f.osfile, nil
+	}
+
+	osfile, err := os.Open(path.Join(f.dataDir, f.filePath))
+	if err != nil {
+		return nil, err
+	}
+	f.osfile = osfile
+
+	return f.osfile, nil
+}
+
+type fileInfo struct {
+	name string
+	size int64
+}
+
+var _ fs.FileInfo = (*fileInfo)(nil)
+
+// IsDir implements fs.FileInfo.
+func (f *fileInfo) IsDir() bool {
+	return false
+}
+
+// ModTime implements fs.FileInfo.
+func (f *fileInfo) ModTime() time.Time {
+	return time.Time{}
+}
+
+// Mode implements fs.FileInfo.
+func (f *fileInfo) Mode() fs.FileMode {
+	return vfs.ROMode
+}
+
+// Name implements fs.FileInfo.
+func (f *fileInfo) Name() string {
+	return f.name
+}
+
+// Size implements fs.FileInfo.
+func (f *fileInfo) Size() int64 {
+	return f.size
+}
+
+// Sys implements fs.FileInfo.
+func (f *fileInfo) Sys() any {
+	return nil
+}
diff --git a/src/sources/qbittorrent/install.go b/src/sources/qbittorrent/install.go
new file mode 100644
index 0000000..5e47a25
--- /dev/null
+++ b/src/sources/qbittorrent/install.go
@@ -0,0 +1,139 @@
+package qbittorrent
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"os/exec"
+	"path"
+	"runtime"
+	"time"
+
+	"github.com/google/go-github/v63/github"
+	"golang.org/x/sys/cpu"
+)
+
+const (
+	repoOwner = "userdocs"
+	repoName  = "qbittorrent-nox-static"
+	binName   = "qbittorrent-nox"
+)
+
+func runQBittorrent(binDir string, profileDir string, stdout, stderr io.Writer) (*os.Process, error) {
+	cmd := exec.Command(
+		path.Join(binDir, binName),
+		fmt.Sprintf("--profile=%s", profileDir),
+	)
+	cmd.Stdin = bytes.NewReader([]byte("y\n"))
+	cmd.Stdout = stdout
+	cmd.Stderr = stderr
+	err := cmd.Start()
+	if err != nil {
+		return nil, err
+	}
+	return cmd.Process, nil
+}
+
+func downloadLatestRelease(ctx context.Context, binPath string) error {
+	client := github.NewClient(nil)
+	rel, _, err := client.Repositories.GetLatestRelease(ctx, repoOwner, repoName)
+	if err != nil {
+		return err
+	}
+
+	arch := ""
+	switch runtime.GOARCH {
+	case "amd64":
+		arch = "x86_64"
+	case "arm":
+		arch = "armhf" // this is a safe version, go does not distinguish between armv6 and armv7
+		if cpu.ARM.HasNEON {
+			arch = "armv7"
+		}
+	case "arm64":
+		arch = "aarch64"
+	}
+
+	if arch == "" {
+		return errors.New("unsupported architecture")
+	}
+
+	binName := arch + "-qbittorrent-nox"
+
+	var targetRelease *github.ReleaseAsset
+	for _, v := range rel.Assets {
+		if v.GetName() == binName {
+			targetRelease = v
+			break
+		}
+	}
+	if targetRelease == nil {
+		return fmt.Errorf("target asset %s not found", binName)
+	}
+
+	downloadUrl := targetRelease.GetBrowserDownloadURL()
+	if downloadUrl == "" {
+		return errors.New("download url is empty")
+	}
+
+	err = os.MkdirAll(path.Dir(binPath), 0755)
+	if err != nil {
+		return err
+	}
+	return downloadFile(binPath, downloadUrl)
+}
+
+func downloadFile(filepath string, webUrl string) error {
+	if stat, err := os.Stat(filepath); err == nil {
+		resp, err := http.Head(webUrl)
+		if err != nil {
+			return err
+		}
+		defer resp.Body.Close()
+
+		var lastModified time.Time
+
+		lastModifiedHeader := resp.Header.Get("Last-Modified")
+		if lastModifiedHeader != "" {
+			lastModified, err = time.Parse(http.TimeFormat, lastModifiedHeader)
+			if err != nil {
+				return err
+			}
+		}
+
+		if resp.ContentLength == stat.Size() && lastModified.Before(stat.ModTime()) {
+			return nil
+		}
+	}
+
+	// Create the file
+	out, err := os.Create(filepath)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+
+	// Get the data
+	resp, err := http.Get(webUrl)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	// Check server response
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("bad status: %s", resp.Status)
+	}
+
+	// Writer the body to file
+	_, err = io.Copy(out, resp.Body)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/src/sources/qbittorrent/install_test.go b/src/sources/qbittorrent/install_test.go
new file mode 100644
index 0000000..62f4975
--- /dev/null
+++ b/src/sources/qbittorrent/install_test.go
@@ -0,0 +1,18 @@
+package qbittorrent
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestDownloadQBittorent(t *testing.T) {
+	ctx := context.Background()
+	tempDir := t.TempDir()
+	require := require.New(t)
+	err := downloadLatestRelease(ctx, tempDir)
+	require.NoError(err)
+	err = downloadLatestRelease(ctx, tempDir)
+	require.NoError(err)
+}
diff --git a/src/sources/torrent/client.go b/src/sources/torrent/client.go
index 9152a83..e6273d7 100644
--- a/src/sources/torrent/client.go
+++ b/src/sources/torrent/client.go
@@ -1,71 +1,110 @@
 package torrent
 
 import (
+	"crypto/rand"
 	"log/slog"
+	"os"
 
-	"github.com/anacrolix/dht/v2"
+	"git.kmsign.ru/royalcat/tstor/src/config"
+	dlog "git.kmsign.ru/royalcat/tstor/src/log"
 	"github.com/anacrolix/dht/v2/bep44"
 	tlog "github.com/anacrolix/log"
 	"github.com/anacrolix/torrent"
 	"github.com/anacrolix/torrent/storage"
-
-	"git.kmsign.ru/royalcat/tstor/src/config"
-	dlog "git.kmsign.ru/royalcat/tstor/src/log"
+	"github.com/anacrolix/torrent/types/infohash"
 )
 
-func newClient(st storage.ClientImpl, fis bep44.Store, cfg *config.TorrentClient, id [20]byte) (*torrent.Client, error) {
+func newClientConfig(st storage.ClientImpl, fis bep44.Store, cfg *config.TorrentClient, id [20]byte) *torrent.ClientConfig {
 	l := slog.With("component", "torrent-client")
 
 	// TODO download and upload limits
 	torrentCfg := torrent.NewDefaultClientConfig()
 	torrentCfg.PeerID = string(id[:])
 	torrentCfg.DefaultStorage = st
-	torrentCfg.AlwaysWantConns = true
+	// torrentCfg.AlwaysWantConns = true
 	torrentCfg.DropMutuallyCompletePeers = true
-	torrentCfg.TorrentPeersLowWater = 100
-	torrentCfg.TorrentPeersHighWater = 1000
-	torrentCfg.AcceptPeerConnections = true
+	// torrentCfg.TorrentPeersLowWater = 100
+	// torrentCfg.TorrentPeersHighWater = 1000
+	// torrentCfg.AcceptPeerConnections = true
 	torrentCfg.Seed = true
 	torrentCfg.DisableAggressiveUpload = false
 
-	tl := tlog.NewLogger()
+	torrentCfg.PeriodicallyAnnounceTorrentsToDht = true
+	// torrentCfg.ConfigureAnacrolixDhtServer = func(cfg *dht.ServerConfig) {
+	// 	cfg.Store = fis
+	// 	cfg.Exp = dhtTTL
+	// 	cfg.PeerStore = fis
+	// }
+
+	tl := tlog.NewLogger("torrent-client")
 	tl.SetHandlers(&dlog.Torrent{L: l})
 
 	torrentCfg.Logger = tl
 	torrentCfg.Callbacks.NewPeer = append(torrentCfg.Callbacks.NewPeer, func(p *torrent.Peer) {
-		l := l.With("ip", p.RemoteAddr.String())
-		if p.Torrent() != nil {
-			l = l.With("torrent", p.Torrent().Name())
-		}
-
-		l.Debug("new peer")
-
+		l.With(peerAttrs(p)...).Debug("new peer")
 	})
-
 	torrentCfg.Callbacks.PeerClosed = append(torrentCfg.Callbacks.PeerClosed, func(p *torrent.Peer) {
-		l := l.With("ip", p.RemoteAddr.String())
-		if p.Torrent() != nil {
-			l = l.With("torrent", p.Torrent().Name())
-		}
-
-		l.Debug("peer closed")
+		l.With(peerAttrs(p)...).Debug("peer closed")
+	})
+	torrentCfg.Callbacks.CompletedHandshake = func(pc *torrent.PeerConn, ih infohash.T) {
+		attrs := append(peerAttrs(&pc.Peer), slog.String("infohash", ih.HexString()))
+		l.With(attrs...).Debug("completed handshake")
+	}
+	torrentCfg.Callbacks.PeerConnAdded = append(torrentCfg.Callbacks.PeerConnAdded, func(pc *torrent.PeerConn) {
+		l.With(peerAttrs(&pc.Peer)...).Debug("peer conn added")
+	})
+	torrentCfg.Callbacks.PeerConnClosed = func(pc *torrent.PeerConn) {
+		l.With(peerAttrs(&pc.Peer)...).Debug("peer conn closed")
+	}
+	torrentCfg.Callbacks.CompletedHandshake = func(pc *torrent.PeerConn, ih infohash.T) {
+		attrs := append(peerAttrs(&pc.Peer), slog.String("infohash", ih.HexString()))
+		l.With(attrs...).Debug("completed handshake")
+	}
+	torrentCfg.Callbacks.ReceivedRequested = append(torrentCfg.Callbacks.ReceivedRequested, func(pme torrent.PeerMessageEvent) {
+		l.With(peerAttrs(pme.Peer)...).Debug("received requested")
+	})
+	torrentCfg.Callbacks.ReceivedUsefulData = append(torrentCfg.Callbacks.ReceivedUsefulData, func(pme torrent.PeerMessageEvent) {
+		l.With(peerAttrs(pme.Peer)...).Debug("received useful data")
 	})
 
-	// torrentCfg.Callbacks.PeerConnClosed = append(torrentCfg.Callbacks.PeerConnClosed, func(c *torrent.PeerConn) {
-	// 	l.Debug("peer closed", "ip", c.RemoteAddr.String())
-	// })
-
-	torrentCfg.PeriodicallyAnnounceTorrentsToDht = true
-	torrentCfg.ConfigureAnacrolixDhtServer = func(cfg *dht.ServerConfig) {
-		cfg.Store = fis
-		cfg.Exp = dhtTTL
-		cfg.NoSecurity = false
-	}
-
-	t, err := torrent.NewClient(torrentCfg)
-	if err != nil {
-		return nil, err
-	}
-
-	return t, nil
+	return torrentCfg
+}
+
+var emptyBytes [20]byte
+
+func getOrCreatePeerID(p string) ([20]byte, error) {
+	idb, err := os.ReadFile(p)
+	if err == nil {
+		var out [20]byte
+		copy(out[:], idb)
+
+		return out, nil
+	}
+
+	if !os.IsNotExist(err) {
+		return emptyBytes, err
+	}
+
+	var out [20]byte
+	_, err = rand.Read(out[:])
+	if err != nil {
+		return emptyBytes, err
+	}
+
+	return out, os.WriteFile(p, out[:], 0755)
+}
+
+func peerAttrs(peer *torrent.Peer) []any {
+	out := []any{
+		slog.String("ip", peer.RemoteAddr.String()),
+		slog.String("discovery", string(peer.Discovery)),
+		slog.Int("max-requests", peer.PeerMaxRequests),
+		slog.Bool("prefers-encryption", peer.PeerPrefersEncryption),
+	}
+
+	if peer.Torrent() != nil {
+		out = append(out, slog.String("torrent", peer.Torrent().Name()))
+	}
+
+	return out
 }
diff --git a/src/sources/torrent/daemon.go b/src/sources/torrent/daemon.go
index 4074dcd..a627f6a 100644
--- a/src/sources/torrent/daemon.go
+++ b/src/sources/torrent/daemon.go
@@ -1,25 +1,21 @@
 package torrent
 
 import (
-	"bufio"
 	"context"
 	"errors"
 	"fmt"
-	"log/slog"
 	"os"
 	"path/filepath"
-	"strings"
 	"sync"
 	"time"
 
-	"git.kmsign.ru/royalcat/tstor/pkg/ctxbilly"
 	"git.kmsign.ru/royalcat/tstor/pkg/rlog"
 	"git.kmsign.ru/royalcat/tstor/src/config"
 	"git.kmsign.ru/royalcat/tstor/src/tkv"
 	"git.kmsign.ru/royalcat/tstor/src/vfs"
-	"github.com/royalcat/ctxio"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
+	"go.opentelemetry.io/otel/metric"
 	"go.opentelemetry.io/otel/trace"
 	"golang.org/x/exp/maps"
 
@@ -28,12 +24,14 @@ import (
 	"github.com/anacrolix/torrent/metainfo"
 	"github.com/anacrolix/torrent/types/infohash"
 	"github.com/go-git/go-billy/v5"
-	"github.com/go-git/go-billy/v5/util"
 	"github.com/royalcat/kv"
 )
 
-var tracer = otel.Tracer("git.kmsign.ru/royalcat/tstor/sources/torrent",
-	trace.WithInstrumentationAttributes(attribute.String("component", "torrent-daemon")),
+const instrument = "git.kmsign.ru/royalcat/tstor/sources/torrent"
+
+var (
+	tracer = otel.Tracer(instrument, trace.WithInstrumentationAttributes(attribute.String("component", "torrent-daemon")))
+	meter  = otel.Meter(instrument, metric.WithInstrumentationAttributes(attribute.String("component", "torrent-daemon")))
 )
 
 type DirAquire struct {
@@ -50,22 +48,20 @@ type Daemon struct {
 	fileProperties kv.Store[string, FileProperties]
 	statsStore     *statsStore
 
-	loadMutex     sync.Mutex
-	torrentLoaded chan struct{}
+	loadMutex sync.Mutex
 
 	sourceFs billy.Filesystem
 
 	log *rlog.Logger
 }
 
-const dhtTTL = 24 * time.Hour
+const dhtTTL = 180 * 24 * time.Hour
 
-func NewService(sourceFs billy.Filesystem, conf config.TorrentClient) (*Daemon, error) {
+func NewDaemon(sourceFs billy.Filesystem, conf config.TorrentClient) (*Daemon, error) {
 	s := &Daemon{
-		log:           rlog.Component("torrent-service"),
-		sourceFs:      sourceFs,
-		torrentLoaded: make(chan struct{}),
-		loadMutex:     sync.Mutex{},
+		log:       rlog.Component("torrent-service"),
+		sourceFs:  sourceFs,
+		loadMutex: sync.Mutex{},
 	}
 
 	err := os.MkdirAll(conf.MetadataFolder, 0744)
@@ -103,10 +99,21 @@ func NewService(sourceFs billy.Filesystem, conf config.TorrentClient) (*Daemon,
 		return nil, err
 	}
 
-	s.client, err = newClient(s.Storage, s.fis, &conf, id)
+	clientConfig := newClientConfig(s.Storage, s.fis, &conf, id)
+	s.client, err = torrent.NewClient(clientConfig)
 	if err != nil {
-		return nil, fmt.Errorf("error starting torrent client: %w", err)
+		return nil, err
 	}
+
+	// TODO move to config
+	s.client.AddDhtNodes([]string{
+		"router.bittorrent.com:6881",
+		"router.utorrent.com:6881",
+		"dht.transmissionbt.com:6881",
+		"router.bitcomet.com:6881",
+		"dht.aelitis.com6881",
+	})
+
 	s.client.AddDhtNodes(conf.DHTNodes)
 
 	s.dirsAquire, err = tkv.NewKV[string, DirAquire](conf.MetadataFolder, "dir-acquire")
@@ -116,17 +123,24 @@ func NewService(sourceFs billy.Filesystem, conf config.TorrentClient) (*Daemon,
 
 	go func() {
 		ctx := context.Background()
-		err := s.loadTorrentFiles(ctx)
+		err := s.backgroudFileLoad(ctx)
 		if err != nil {
 			s.log.Error(ctx, "initial torrent load failed", rlog.Error(err))
 		}
-		close(s.torrentLoaded)
 	}()
 
 	go func() {
+		ctx := context.Background()
 		const period = time.Second * 10
 
-		<-s.torrentLoaded
+		err := registerTorrentMetrics(s.client)
+		if err != nil {
+			s.log.Error(ctx, "error registering torrent metrics", rlog.Error(err))
+		}
+		err = registerDhtMetrics(s.client)
+		if err != nil {
+			s.log.Error(ctx, "error registering dht metrics", rlog.Error(err))
+		}
 
 		timer := time.NewTicker(period)
 		for {
@@ -134,7 +148,7 @@ func NewService(sourceFs billy.Filesystem, conf config.TorrentClient) (*Daemon,
 			case <-s.client.Closed():
 				return
 			case <-timer.C:
-				s.updateStats(context.Background())
+				s.updateStats(ctx)
 			}
 		}
 	}()
@@ -142,58 +156,6 @@ func NewService(sourceFs billy.Filesystem, conf config.TorrentClient) (*Daemon,
 	return s, nil
 }
 
-func (s *Daemon) updateStats(ctx context.Context) {
-	log := s.log
-
-	totalPeers := 0
-	activePeers := 0
-	connectedSeeders := 0
-
-	for _, v := range s.client.Torrents() {
-		stats := v.Stats()
-		err := s.statsStore.AddTorrentStats(v.InfoHash(), TorrentStats{
-			Timestamp:        time.Now(),
-			DownloadedBytes:  uint64(stats.BytesRead.Int64()),
-			UploadedBytes:    uint64(stats.BytesWritten.Int64()),
-			TotalPeers:       uint16(stats.TotalPeers),
-			ActivePeers:      uint16(stats.ActivePeers),
-			ConnectedSeeders: uint16(stats.ConnectedSeeders),
-		})
-		if err != nil {
-			log.Error(ctx, "error saving torrent stats", rlog.Error(err))
-		}
-
-		totalPeers += stats.TotalPeers
-		activePeers += stats.ActivePeers
-		connectedSeeders += stats.ConnectedSeeders
-	}
-
-	totalStats := s.client.Stats()
-	err := s.statsStore.AddTotalStats(TorrentStats{
-		Timestamp:        time.Now(),
-		DownloadedBytes:  uint64(totalStats.BytesRead.Int64()),
-		UploadedBytes:    uint64(totalStats.BytesWritten.Int64()),
-		TotalPeers:       uint16(totalPeers),
-		ActivePeers:      uint16(activePeers),
-		ConnectedSeeders: uint16(connectedSeeders),
-	})
-	if err != nil {
-		log.Error(ctx, "error saving total stats", rlog.Error(err))
-	}
-}
-
-func (s *Daemon) TotalStatsHistory(ctx context.Context, since time.Time) ([]TorrentStats, error) {
-	return s.statsStore.ReadTotalStatsHistory(ctx, since)
-}
-
-func (s *Daemon) TorrentStatsHistory(ctx context.Context, since time.Time, ih infohash.T) ([]TorrentStats, error) {
-	return s.statsStore.ReadTorrentStatsHistory(ctx, since, ih)
-}
-
-func (s *Daemon) StatsHistory(ctx context.Context, since time.Time) ([]TorrentStats, error) {
-	return s.statsStore.ReadStatsHistory(ctx, since)
-}
-
 var _ vfs.FsFactory = (*Daemon)(nil).NewTorrentFs
 
 func (s *Daemon) Close(ctx context.Context) error {
@@ -207,104 +169,6 @@ func (s *Daemon) Close(ctx context.Context) error {
 	)...)
 }
 
-func (s *Daemon) loadTorrent(ctx context.Context, f vfs.File) (*Controller, error) {
-	ctx, span := tracer.Start(ctx, "loadTorrent")
-	defer span.End()
-	log := s.log
-
-	stat, err := f.Info()
-	if err != nil {
-		return nil, fmt.Errorf("call stat failed: %w", err)
-	}
-
-	span.SetAttributes(attribute.String("filename", stat.Name()))
-
-	mi, err := metainfo.Load(bufio.NewReader(ctxio.IoReader(ctx, f)))
-	if err != nil {
-		return nil, fmt.Errorf("loading torrent metadata from file %s, error: %w", stat.Name(), err)
-	}
-
-	var ctl *Controller
-	t, ok := s.client.Torrent(mi.HashInfoBytes())
-	if ok {
-		ctl = s.newController(t)
-	} else {
-		span.AddEvent("torrent not found, loading from file")
-		log.Info(ctx, "torrent not found, loading from file")
-
-		spec, err := torrent.TorrentSpecFromMetaInfoErr(mi)
-		if err != nil {
-			return nil, fmt.Errorf("parse spec from metadata: %w", err)
-		}
-		infoBytes := spec.InfoBytes
-
-		if !isValidInfoHashBytes(infoBytes) {
-			log.Warn(ctx, "info loaded from spec not valid")
-			infoBytes = nil
-		}
-
-		if len(infoBytes) == 0 {
-			log.Info(ctx, "no info loaded from file, try to load from cache")
-			infoBytes, err = s.infoBytes.GetBytes(spec.InfoHash)
-			if err != nil && err != errNotFound {
-				return nil, fmt.Errorf("get info bytes from database: %w", err)
-			}
-		}
-
-		t, _ = s.client.AddTorrentOpt(torrent.AddTorrentOpts{
-			InfoHash:   spec.InfoHash,
-			InfoHashV2: spec.InfoHashV2,
-			Storage:    s.Storage,
-			InfoBytes:  infoBytes,
-			ChunkSize:  spec.ChunkSize,
-		})
-
-		t.AllowDataDownload()
-		t.AllowDataUpload()
-
-		span.AddEvent("torrent added to client")
-
-		select {
-		case <-ctx.Done():
-			return nil, ctx.Err()
-		case <-t.GotInfo():
-			err := s.infoBytes.Set(t.InfoHash(), t.Metainfo())
-			if err != nil {
-				log.Error(ctx, "error setting info bytes for torrent",
-					slog.String("torrent-name", t.Name()),
-					rlog.Error(err),
-				)
-			}
-		}
-		span.AddEvent("got info")
-
-		ctl = s.newController(t)
-
-		err = ctl.initializeTorrentPriories(ctx)
-		if err != nil {
-			return nil, fmt.Errorf("initialize torrent priorities: %w", err)
-		}
-
-		// info := t.Info()
-		// if info == nil {
-		// 	return nil, fmt.Errorf("info is nil")
-		// }
-
-		// 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(),
-		// 	)
-		// }
-	}
-
-	return ctl, nil
-}
-
 func isValidInfoHashBytes(d []byte) bool {
 	var info metainfo.Info
 	err := bencode.Unmarshal(d, &info)
@@ -315,74 +179,6 @@ func (s *Daemon) Stats() torrent.ConnStats {
 	return s.client.Stats().ConnStats
 }
 
-const loadWorkers = 5
-
-func (s *Daemon) loadTorrentFiles(ctx context.Context) error {
-	ctx, span := tracer.Start(ctx, "loadTorrentFiles", trace.WithAttributes(
-		attribute.Int("workers", loadWorkers),
-	))
-	defer span.End()
-	log := s.log
-
-	loaderPaths := make(chan string, loadWorkers*5)
-	wg := sync.WaitGroup{}
-
-	defer func() {
-		close(loaderPaths)
-		wg.Wait()
-	}()
-
-	loaderWorker := func() {
-		for path := range loaderPaths {
-			info, err := s.sourceFs.Stat(path)
-			if err != nil {
-				log.Error(ctx, "error stat torrent file", slog.String("filename", path), rlog.Error(err))
-				continue
-			}
-
-			file, err := s.sourceFs.Open(path)
-			if err != nil {
-				log.Error(ctx, "error opening torrent file", slog.String("filename", path), rlog.Error(err))
-				continue
-			}
-			defer file.Close()
-
-			vfile := vfs.NewCtxBillyFile(info, ctxbilly.WrapFile(file))
-
-			_, err = s.loadTorrent(ctx, vfile)
-			if err != nil {
-				log.Error(ctx, "failed adding torrent", rlog.Error(err))
-			}
-		}
-		wg.Done()
-	}
-
-	wg.Add(loadWorkers)
-	for range loadWorkers {
-		go loaderWorker()
-	}
-
-	return util.Walk(s.sourceFs, "", func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return fmt.Errorf("fs walk error: %w", err)
-		}
-
-		if ctx.Err() != nil {
-			return ctx.Err()
-		}
-
-		if info.IsDir() {
-			return nil
-		}
-
-		if strings.HasSuffix(path, ".torrent") {
-			loaderPaths <- path
-		}
-
-		return nil
-	})
-}
-
 func storeByTorrent[K kv.Bytes, V any](s kv.Store[K, V], infohash infohash.T) kv.Store[K, V] {
 	return kv.PrefixBytes[K, V](s, K(infohash.HexString()+"/"))
 }
@@ -396,8 +192,6 @@ func (s *Daemon) newController(t *torrent.Torrent) *Controller {
 }
 
 func (s *Daemon) ListTorrents(ctx context.Context) ([]*Controller, error) {
-	<-s.torrentLoaded
-
 	out := []*Controller{}
 	for _, v := range s.client.Torrents() {
 		out = append(out, s.newController(v))
@@ -406,8 +200,6 @@ func (s *Daemon) ListTorrents(ctx context.Context) ([]*Controller, error) {
 }
 
 func (s *Daemon) GetTorrent(infohashHex string) (*Controller, error) {
-	<-s.torrentLoaded
-
 	t, ok := s.client.Torrent(infohash.FromHexString(infohashHex))
 	if !ok {
 		return nil, nil
diff --git a/src/sources/torrent/daemon_load.go b/src/sources/torrent/daemon_load.go
new file mode 100644
index 0000000..9793e98
--- /dev/null
+++ b/src/sources/torrent/daemon_load.go
@@ -0,0 +1,246 @@
+package torrent
+
+import (
+	"bufio"
+	"context"
+	"fmt"
+	"io"
+	"log/slog"
+	"os"
+	"strings"
+	"sync"
+	"time"
+
+	"git.kmsign.ru/royalcat/tstor/pkg/ctxbilly"
+	"git.kmsign.ru/royalcat/tstor/pkg/rlog"
+	"git.kmsign.ru/royalcat/tstor/src/vfs"
+	"github.com/anacrolix/torrent"
+	"github.com/anacrolix/torrent/metainfo"
+	"github.com/go-git/go-billy/v5/util"
+	"github.com/royalcat/ctxio"
+	"go.opentelemetry.io/otel/attribute"
+	"go.opentelemetry.io/otel/trace"
+)
+
+const activityTimeout = time.Minute * 15
+
+func readInfoHash(ctx context.Context, f vfs.File) (metainfo.Hash, error) {
+	ctx, span := tracer.Start(ctx, "readInfoHash")
+	defer span.End()
+
+	mi, err := metainfo.Load(ctxio.IoReader(ctx, f))
+	if err != nil {
+		return metainfo.Hash{}, fmt.Errorf("loading metainfo: %w", err)
+	}
+
+	return mi.HashInfoBytes(), nil
+}
+
+func (s *Daemon) loadTorrent(ctx context.Context, f vfs.File) (*Controller, error) {
+	ctx, span := tracer.Start(ctx, "loadTorrent")
+	defer span.End()
+	log := s.log
+
+	stat, err := f.Info()
+	if err != nil {
+		return nil, fmt.Errorf("call stat failed: %w", err)
+	}
+
+	span.SetAttributes(attribute.String("filename", stat.Name()))
+
+	mi, err := metainfo.Load(bufio.NewReader(ctxio.IoReader(ctx, f)))
+	if err != nil {
+		return nil, fmt.Errorf("loading torrent metadata from file %s, error: %w", stat.Name(), err)
+	}
+	log = log.With(slog.String("info-hash", mi.HashInfoBytes().HexString()))
+
+	var ctl *Controller
+	t, ok := s.client.Torrent(mi.HashInfoBytes())
+	if ok {
+		log = log.With(slog.String("torrent-name", t.Name()))
+		ctl = s.newController(t)
+	} else {
+		span.AddEvent("torrent not found, loading from file")
+		log.Info(ctx, "torrent not found, loading from file")
+
+		spec, err := torrent.TorrentSpecFromMetaInfoErr(mi)
+		if err != nil {
+			return nil, fmt.Errorf("parse spec from metadata: %w", err)
+		}
+		infoBytes := spec.InfoBytes
+
+		if !isValidInfoHashBytes(infoBytes) {
+			log.Warn(ctx, "info loaded from spec not valid")
+			infoBytes = nil
+		}
+
+		if len(infoBytes) == 0 {
+			log.Info(ctx, "no info loaded from file, try to load from cache")
+			infoBytes, err = s.infoBytes.GetBytes(spec.InfoHash)
+			if err != nil && err != errNotFound {
+				return nil, fmt.Errorf("get info bytes from database: %w", err)
+			}
+		}
+
+		t, _ = s.client.AddTorrentOpt(torrent.AddTorrentOpts{
+			InfoHash:   spec.InfoHash,
+			InfoHashV2: spec.InfoHashV2,
+			Storage:    s.Storage,
+			InfoBytes:  infoBytes,
+			ChunkSize:  spec.ChunkSize,
+		})
+
+		log = log.With(slog.String("torrent-name", t.Name()))
+
+		t.AllowDataDownload()
+		t.AllowDataUpload()
+
+		span.AddEvent("torrent added to client")
+
+		select {
+		case <-ctx.Done():
+			return nil, ctx.Err()
+		case <-t.GotInfo():
+			err := s.infoBytes.Set(t.InfoHash(), t.Metainfo())
+			if err != nil {
+				log.Error(ctx, "error setting info bytes for torrent",
+					slog.String("torrent-name", t.Name()),
+					rlog.Error(err),
+				)
+			}
+		}
+		span.AddEvent("got info")
+
+		ctl = s.newController(t)
+
+		err = ctl.initializeTorrentPriories(ctx)
+		if err != nil {
+			return nil, fmt.Errorf("initialize torrent priorities: %w", err)
+		}
+
+		// go func() {
+		// 	subscr := ctl.t.SubscribePieceStateChanges()
+		// 	defer subscr.Close()
+		// 	dropTimer := time.NewTimer(activityTimeout)
+		// 	defer dropTimer.Stop()
+
+		// 	for {
+		// 		select {
+		// 		case <-subscr.Values:
+		// 			dropTimer.Reset(activityTimeout)
+		// 		case <-dropTimer.C:
+		// 			log.Info(ctx, "torrent dropped by activity timeout")
+		// 			select {
+		// 			case <-ctl.t.Closed():
+		// 				return
+		// 			case <-time.After(time.Second):
+		// 				ctl.t.Drop()
+		// 			}
+		// 		case <-ctl.t.Closed():
+		// 			return
+		// 		}
+		// 	}
+		// }()
+	}
+
+	return ctl, nil
+}
+
+const loadWorkers = 5
+
+func (s *Daemon) backgroudFileLoad(ctx context.Context) error {
+	ctx, span := tracer.Start(ctx, "loadTorrentFiles", trace.WithAttributes(
+		attribute.Int("workers", loadWorkers),
+	))
+	defer span.End()
+	log := s.log
+
+	loaderPaths := make(chan string, loadWorkers*5)
+	wg := sync.WaitGroup{}
+
+	defer func() {
+		close(loaderPaths)
+		wg.Wait()
+	}()
+
+	loaderWorker := func() {
+		for path := range loaderPaths {
+			info, err := s.sourceFs.Stat(path)
+			if err != nil {
+				log.Error(ctx, "error stat torrent file", slog.String("filename", path), rlog.Error(err))
+				continue
+			}
+
+			file, err := s.sourceFs.Open(path)
+			if err != nil {
+				log.Error(ctx, "error opening torrent file", slog.String("filename", path), rlog.Error(err))
+				continue
+			}
+			defer file.Close()
+
+			vfile := vfs.NewCtxBillyFile(info, ctxbilly.WrapFile(file))
+
+			ih, err := readInfoHash(ctx, vfile)
+			if err != nil {
+				log.Error(ctx, "error reading info hash", slog.String("filename", path), rlog.Error(err))
+				continue
+			}
+			props := storeByTorrent(s.fileProperties, ih)
+			_, err = vfile.Seek(0, io.SeekStart)
+			if err != nil {
+				log.Error(ctx, "error seeking file", slog.String("filename", path), rlog.Error(err))
+				continue
+			}
+
+			isPrioritized := false
+			err = props.Range(ctx, func(k string, v FileProperties) error {
+				if v.Priority > 0 {
+					isPrioritized = true
+					return io.EOF
+				}
+				return nil
+			})
+			if err != nil && err != io.EOF {
+				log.Error(ctx, "error checking file priority", slog.String("filename", path), rlog.Error(err))
+				continue
+			}
+
+			if !isPrioritized {
+				log.Debug(ctx, "file not prioritized, skipping", slog.String("filename", path))
+				continue
+			}
+
+			_, err = s.loadTorrent(ctx, vfile)
+			if err != nil {
+				log.Error(ctx, "failed adding torrent", rlog.Error(err))
+			}
+		}
+
+		wg.Done()
+	}
+
+	wg.Add(loadWorkers)
+	for range loadWorkers {
+		go loaderWorker()
+	}
+
+	return util.Walk(s.sourceFs, "", func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return fmt.Errorf("fs walk error: %w", err)
+		}
+
+		if ctx.Err() != nil {
+			return ctx.Err()
+		}
+
+		if info.IsDir() {
+			return nil
+		}
+
+		if strings.HasSuffix(path, ".torrent") {
+			loaderPaths <- path
+		}
+
+		return nil
+	})
+}
diff --git a/src/sources/torrent/daemon_stats.go b/src/sources/torrent/daemon_stats.go
new file mode 100644
index 0000000..cce0701
--- /dev/null
+++ b/src/sources/torrent/daemon_stats.go
@@ -0,0 +1,73 @@
+package torrent
+
+import (
+	"context"
+	"time"
+
+	"git.kmsign.ru/royalcat/tstor/pkg/rlog"
+	"github.com/anacrolix/torrent/types/infohash"
+)
+
+func (s *Daemon) allStats(ctx context.Context) (map[infohash.T]TorrentStats, TorrentStats) {
+	totalPeers := 0
+	activePeers := 0
+	connectedSeeders := 0
+
+	perTorrentStats := map[infohash.T]TorrentStats{}
+
+	for _, v := range s.client.Torrents() {
+		stats := v.Stats()
+		perTorrentStats[v.InfoHash()] = TorrentStats{
+			Timestamp:        time.Now(),
+			DownloadedBytes:  uint64(stats.BytesRead.Int64()),
+			UploadedBytes:    uint64(stats.BytesWritten.Int64()),
+			TotalPeers:       uint16(stats.TotalPeers),
+			ActivePeers:      uint16(stats.ActivePeers),
+			ConnectedSeeders: uint16(stats.ConnectedSeeders),
+		}
+
+		totalPeers += stats.TotalPeers
+		activePeers += stats.ActivePeers
+		connectedSeeders += stats.ConnectedSeeders
+	}
+
+	totalStats := s.client.Stats()
+
+	return perTorrentStats, TorrentStats{
+		Timestamp:        time.Now(),
+		DownloadedBytes:  uint64(totalStats.BytesRead.Int64()),
+		UploadedBytes:    uint64(totalStats.BytesWritten.Int64()),
+		TotalPeers:       uint16(totalPeers),
+		ActivePeers:      uint16(activePeers),
+		ConnectedSeeders: uint16(connectedSeeders),
+	}
+}
+
+func (s *Daemon) updateStats(ctx context.Context) {
+	log := s.log
+
+	perTorrentStats, totalStats := s.allStats(ctx)
+	for ih, v := range perTorrentStats {
+		err := s.statsStore.AddTorrentStats(ih, v)
+		if err != nil {
+			log.Error(ctx, "error saving torrent stats", rlog.Error(err))
+		}
+	}
+
+	err := s.statsStore.AddTotalStats(totalStats)
+	if err != nil {
+		log.Error(ctx, "error saving total stats", rlog.Error(err))
+	}
+}
+
+func (s *Daemon) TotalStatsHistory(ctx context.Context, since time.Time) ([]TorrentStats, error) {
+	return s.statsStore.ReadTotalStatsHistory(ctx, since)
+}
+
+func (s *Daemon) TorrentStatsHistory(ctx context.Context, since time.Time, ih infohash.T) ([]TorrentStats, error) {
+	return s.statsStore.ReadTorrentStatsHistory(ctx, since, ih)
+}
+
+func (s *Daemon) StatsHistory(ctx context.Context, since time.Time) ([]TorrentStats, error) {
+	return s.statsStore.ReadStatsHistory(ctx, since)
+}
diff --git a/src/sources/torrent/fs.go b/src/sources/torrent/fs.go
index fd06cbf..6549389 100644
--- a/src/sources/torrent/fs.go
+++ b/src/sources/torrent/fs.go
@@ -410,7 +410,6 @@ func openTorrentFile(ctx context.Context, name string, file *torrent.File, lastT
 	}
 
 	r := file.NewReader()
-	r.SetReadahead(1024 * 1024 * 16) // TODO configurable
 	_, err := r.ReadContext(ctx, make([]byte, 128))
 	if err != nil && err != io.EOF {
 		return nil, fmt.Errorf("failed initial file read: %w", err)
@@ -433,6 +432,14 @@ func (tf *torrentFile) Name() string {
 	return tf.name
 }
 
+// Seek implements vfs.File.
+func (tf *torrentFile) Seek(offset int64, whence int) (int64, error) {
+	tf.mu.Lock()
+	defer tf.mu.Unlock()
+
+	return tf.tr.Seek(offset, whence)
+}
+
 // Type implements File.
 func (tf *torrentFile) Type() fs.FileMode {
 	return vfs.ROMode | fs.ModeDir
@@ -483,8 +490,8 @@ func (tf *torrentFile) Read(ctx context.Context, p []byte) (n int, err error) {
 		span.End()
 	}()
 
-	tf.mu.RLock()
-	defer tf.mu.RUnlock()
+	tf.mu.Lock()
+	defer tf.mu.Unlock()
 
 	ctx, cancel := tf.readTimeout(ctx)
 	defer cancel()
diff --git a/src/sources/torrent/id.go b/src/sources/torrent/id.go
deleted file mode 100644
index 047d8d8..0000000
--- a/src/sources/torrent/id.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package torrent
-
-import (
-	"crypto/rand"
-	"os"
-)
-
-var emptyBytes [20]byte
-
-func getOrCreatePeerID(p string) ([20]byte, error) {
-	idb, err := os.ReadFile(p)
-	if err == nil {
-		var out [20]byte
-		copy(out[:], idb)
-
-		return out, nil
-	}
-
-	if !os.IsNotExist(err) {
-		return emptyBytes, err
-	}
-
-	var out [20]byte
-	_, err = rand.Read(out[:])
-	if err != nil {
-		return emptyBytes, err
-	}
-
-	return out, os.WriteFile(p, out[:], 0755)
-}
diff --git a/src/sources/torrent/metrics.go b/src/sources/torrent/metrics.go
new file mode 100644
index 0000000..7101683
--- /dev/null
+++ b/src/sources/torrent/metrics.go
@@ -0,0 +1,69 @@
+package torrent
+
+import (
+	"context"
+	"encoding/base64"
+
+	"github.com/anacrolix/dht/v2"
+	"github.com/anacrolix/torrent"
+	"go.opentelemetry.io/otel/attribute"
+	"go.opentelemetry.io/otel/metric"
+)
+
+func registerTorrentMetrics(client *torrent.Client) error {
+	meterTotalPeers, _ := meter.Int64ObservableGauge("torrent.peers.total")
+	meterActivePeers, _ := meter.Int64ObservableGauge("torrent.peers.active")
+	meterSeeders, _ := meter.Int64ObservableGauge("torrent.seeders")
+	meterDownloaded, _ := meter.Int64ObservableGauge("torrent.downloaded", metric.WithUnit("By"))
+	meterIO, _ := meter.Int64ObservableGauge("torrent.io", metric.WithUnit("By"))
+	meterLoaded, _ := meter.Int64ObservableGauge("torrent.loaded")
+
+	_, err := meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
+		o.ObserveInt64(meterLoaded, int64(len(client.Torrents())))
+
+		for _, v := range client.Torrents() {
+			as := attribute.NewSet(
+				attribute.String("infohash", v.InfoHash().HexString()),
+				attribute.String("name", v.Name()),
+				attribute.Int64("size", v.Length()),
+			)
+			stats := v.Stats()
+			o.ObserveInt64(meterTotalPeers, int64(stats.TotalPeers), metric.WithAttributeSet(as))
+			o.ObserveInt64(meterActivePeers, int64(stats.ActivePeers), metric.WithAttributeSet(as))
+			o.ObserveInt64(meterSeeders, int64(stats.ConnectedSeeders), metric.WithAttributeSet(as))
+			o.ObserveInt64(meterIO, stats.BytesRead.Int64(), metric.WithAttributeSet(as), metric.WithAttributes(attribute.String("direction", "download")))
+			o.ObserveInt64(meterIO, stats.BytesWritten.Int64(), metric.WithAttributeSet(as), metric.WithAttributes(attribute.String("direction", "upload")))
+			o.ObserveInt64(meterDownloaded, v.BytesCompleted(), metric.WithAttributeSet(as))
+		}
+
+		return nil
+	}, meterTotalPeers, meterActivePeers, meterSeeders, meterIO, meterDownloaded, meterLoaded)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func registerDhtMetrics(client *torrent.Client) error {
+	meterDhtNodes, _ := meter.Int64ObservableGauge("torrent.dht.nodes")
+
+	_, err := meter.RegisterCallback(func(ctx context.Context, o metric.Observer) error {
+		servers := client.DhtServers()
+		for _, dhtSrv := range servers {
+			stats, ok := dhtSrv.Stats().(dht.ServerStats)
+			if !ok {
+				continue
+			}
+			id := dhtSrv.ID()
+			as := attribute.NewSet(
+				attribute.String("id", base64.StdEncoding.EncodeToString(id[:])),
+				attribute.String("address", dhtSrv.Addr().String()),
+			)
+			o.ObserveInt64(meterDhtNodes, int64(stats.Nodes), metric.WithAttributeSet(as))
+		}
+
+		return nil
+	}, meterDhtNodes)
+
+	return err
+}
diff --git a/src/sources/torrent/peer_store.go b/src/sources/torrent/peer_store.go
new file mode 100644
index 0000000..59dcc84
--- /dev/null
+++ b/src/sources/torrent/peer_store.go
@@ -0,0 +1,24 @@
+package torrent
+
+import (
+	"github.com/anacrolix/dht/v2/krpc"
+	peer_store "github.com/anacrolix/dht/v2/peer-store"
+	"github.com/anacrolix/torrent/types/infohash"
+	"github.com/royalcat/kv"
+)
+
+type peerStore struct {
+	store kv.Store[infohash.T, []krpc.NodeAddr]
+}
+
+var _ peer_store.Interface = (*peerStore)(nil)
+
+// AddPeer implements peer_store.Interface.
+func (p *peerStore) AddPeer(ih infohash.T, node krpc.NodeAddr) {
+	panic("unimplemented")
+}
+
+// GetPeers implements peer_store.Interface.
+func (p *peerStore) GetPeers(ih infohash.T) []krpc.NodeAddr {
+	panic("unimplemented")
+}
diff --git a/src/sources/torrent/piece_completion_test.go b/src/sources/torrent/piece_completion_test.go
new file mode 100644
index 0000000..da5a9f6
--- /dev/null
+++ b/src/sources/torrent/piece_completion_test.go
@@ -0,0 +1,36 @@
+package torrent
+
+import (
+	"testing"
+
+	"github.com/anacrolix/torrent/metainfo"
+	"github.com/anacrolix/torrent/storage"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestBoltPieceCompletion(t *testing.T) {
+	td := t.TempDir()
+
+	pc, err := newPieceCompletion(td)
+	require.NoError(t, err)
+	defer pc.Close()
+
+	pk := metainfo.PieceKey{}
+
+	b, err := pc.Get(pk)
+	require.NoError(t, err)
+	assert.False(t, b.Ok)
+
+	require.NoError(t, pc.Set(pk, false))
+
+	b, err = pc.Get(pk)
+	require.NoError(t, err)
+	assert.Equal(t, storage.Completion{Complete: false, Ok: true}, b)
+
+	require.NoError(t, pc.Set(pk, true))
+
+	b, err = pc.Get(pk)
+	require.NoError(t, err)
+	assert.Equal(t, storage.Completion{Complete: true, Ok: true}, b)
+}
diff --git a/src/sources/torrent/storage_open.go b/src/sources/torrent/storage_open.go
index 84b3055..0afa7e9 100644
--- a/src/sources/torrent/storage_open.go
+++ b/src/sources/torrent/storage_open.go
@@ -19,36 +19,14 @@ import (
 )
 
 // OpenTorrent implements storage.ClientImplCloser.
-func (me *fileStorage) OpenTorrent(info *metainfo.Info, infoHash infohash.T) (storage.TorrentImpl, error) {
-	ctx := context.Background()
-	log := me.log.With(slog.String("infohash", infoHash.HexString()))
+func (me *fileStorage) OpenTorrent(ctx context.Context, info *metainfo.Info, infoHash infohash.T) (storage.TorrentImpl, error) {
+	ctx, span := tracer.Start(ctx, "OpenTorrent")
+	defer span.End()
+	log := me.log.With(slog.String("infohash", infoHash.HexString()), slog.String("name", info.BestName()))
 
-	// dir := torrentDir(me.baseDir, infoHash)
-	// legacyDir := filepath.Join(me.baseDir, info.Name)
+	log.Debug(ctx, "opening torrent")
 
-	// log = log.With(slog.String("legacy_dir", legacyDir), slog.String("dir", dir))
-	// if _, err := os.Stat(legacyDir); err == nil {
-	// 	log.Warn(ctx, "legacy torrent dir found, renaming", slog.String("dir", dir))
-	// 	err = os.Rename(legacyDir, dir)
-	// 	if err != nil {
-	// 		return storage.TorrentImpl{}, fmt.Errorf("error renaming legacy torrent dir: %w", err)
-	// 	}
-	// }
-
-	// if _, err := os.Stat(dir); errors.Is(err, fs.ErrNotExist) {
-	// 	log.Info(ctx, "new torrent, trying copy files from existing")
-	// 	dups := me.dupIndex.Includes(infoHash, info.Files)
-
-	// 	for _, dup := range dups {
-	// 		err := me.copyDup(ctx, infoHash, dup)
-	// 		if err != nil {
-	// 			log.Error(ctx, "error copying file", slog.String("file", dup.fileinfo.DisplayPath(info)), rlog.Error(err))
-	// 		}
-	// 	}
-
-	// }
-
-	impl, err := me.client.OpenTorrent(info, infoHash)
+	impl, err := me.client.OpenTorrent(ctx, info, infoHash)
 	if err != nil {
 		log.Error(ctx, "error opening torrent", rlog.Error(err))
 	}
diff --git a/src/telemetry/setup.go b/src/telemetry/setup.go
index 796f74d..fa16f48 100644
--- a/src/telemetry/setup.go
+++ b/src/telemetry/setup.go
@@ -12,15 +12,17 @@ import (
 	"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"
+	"github.com/google/uuid"
 	otelpyroscope "github.com/grafana/otel-profiling-go"
 	"github.com/grafana/pyroscope-go"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
 	"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.25.0"
+	semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
 )
 
 type Client struct {
@@ -70,6 +72,7 @@ func Setup(ctx context.Context, endpoint string) (*Client, error) {
 			semconv.SchemaURL,
 			semconv.ServiceName(appName),
 			semconv.HostName(hostName),
+			semconv.ServiceInstanceID(uuid.NewString()),
 		),
 	)
 	if err != nil {
@@ -86,8 +89,13 @@ func Setup(ctx context.Context, endpoint string) (*Client, error) {
 		return nil, err
 	}
 
+	promExporter, err := prometheus.New()
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize prometheus exporter: %w", err)
+	}
 	client.metricProvider = metric.NewMeterProvider(
 		metric.WithReader(metric.NewPeriodicReader(meticExporter)),
+		metric.WithReader(promExporter),
 		metric.WithResource(r),
 	)
 	otel.SetMeterProvider(client.metricProvider)
diff --git a/src/vfs/archive.go b/src/vfs/archive.go
index 0375ef6..47e69ec 100644
--- a/src/vfs/archive.go
+++ b/src/vfs/archive.go
@@ -193,6 +193,20 @@ type archiveFile struct {
 	buffer *filebuffer.Buffer
 }
 
+// Seek implements File.
+func (d *archiveFile) Seek(offset int64, whence int) (int64, error) {
+	switch whence {
+	case io.SeekStart:
+		d.offset = offset
+
+	case io.SeekCurrent:
+		d.offset += offset
+	case io.SeekEnd:
+		d.offset = d.size + offset
+	}
+	return d.offset, nil
+}
+
 // Name implements File.
 func (d *archiveFile) Name() string {
 	return d.name
diff --git a/src/vfs/ctxbillyfs.go b/src/vfs/ctxbillyfs.go
index 6613eac..c7c64e8 100644
--- a/src/vfs/ctxbillyfs.go
+++ b/src/vfs/ctxbillyfs.go
@@ -110,6 +110,11 @@ type CtxBillyFile struct {
 	file ctxbilly.File
 }
 
+// Seek implements File.
+func (c *CtxBillyFile) Seek(offset int64, whence int) (int64, error) {
+	return c.file.Seek(offset, whence)
+}
+
 // Close implements File.
 func (c *CtxBillyFile) Close(ctx context.Context) error {
 	return c.file.Close(ctx)
diff --git a/src/vfs/dir.go b/src/vfs/dir.go
index c11864a..1ac24e6 100644
--- a/src/vfs/dir.go
+++ b/src/vfs/dir.go
@@ -33,6 +33,11 @@ func (d *dirFile) IsDir() bool {
 	return true
 }
 
+// Seek implements File.
+func (d *dirFile) Seek(offset int64, whence int) (int64, error) {
+	return 0, fs.ErrInvalid
+}
+
 // Name implements File.
 func (d *dirFile) Name() string {
 	return d.name
diff --git a/src/vfs/dummy.go b/src/vfs/dummy.go
index 8f83993..d30ecb1 100644
--- a/src/vfs/dummy.go
+++ b/src/vfs/dummy.go
@@ -79,6 +79,11 @@ type DummyFile struct {
 	name string
 }
 
+// Seek implements File.
+func (d *DummyFile) Seek(offset int64, whence int) (int64, error) {
+	return 0, nil
+}
+
 // Name implements File.
 func (d *DummyFile) Name() string {
 	panic("unimplemented")
diff --git a/src/vfs/fs.go b/src/vfs/fs.go
index 79cb242..892a01e 100644
--- a/src/vfs/fs.go
+++ b/src/vfs/fs.go
@@ -19,6 +19,7 @@ type File interface {
 	ctxio.Reader
 	ctxio.ReaderAt
 	ctxio.Closer
+	ctxio.Seeker
 }
 
 var ErrNotImplemented = errors.New("not implemented")
diff --git a/src/vfs/log.go b/src/vfs/log.go
index d0a05c7..b21fb10 100644
--- a/src/vfs/log.go
+++ b/src/vfs/log.go
@@ -2,9 +2,7 @@ package vfs
 
 import (
 	"context"
-	"errors"
 	"fmt"
-	"io"
 	"io/fs"
 	"log/slog"
 	"reflect"
@@ -36,7 +34,7 @@ type LogFS struct {
 }
 
 func isLoggableError(err error) bool {
-	return err != nil && !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, io.EOF)
+	return err != nil // && !errors.Is(err, fs.ErrNotExist) && !errors.Is(err, io.EOF)
 }
 
 var _ Filesystem = (*LogFS)(nil)
@@ -209,6 +207,11 @@ type LogFile struct {
 	timeout time.Duration
 }
 
+// Seek implements File.
+func (f *LogFile) Seek(offset int64, whence int) (int64, error) {
+	return f.f.Seek(offset, whence)
+}
+
 // Name implements File.
 func (f *LogFile) Name() string {
 	return f.f.Name()
diff --git a/src/vfs/memory.go b/src/vfs/memory.go
index ce0d63e..e7b8de6 100644
--- a/src/vfs/memory.go
+++ b/src/vfs/memory.go
@@ -108,6 +108,11 @@ func (d *MemoryFile) Name() string {
 	return d.name
 }
 
+// Seek implements File.
+func (d *MemoryFile) Seek(offset int64, whence int) (int64, error) {
+	return d.data.Seek(offset, whence)
+}
+
 // Type implements File.
 func (d *MemoryFile) Type() fs.FileMode {
 	return ROMode
diff --git a/src/vfs/os.go b/src/vfs/os.go
index 7244096..07661f1 100644
--- a/src/vfs/os.go
+++ b/src/vfs/os.go
@@ -122,6 +122,11 @@ func (f *LazyOsFile) Type() fs.FileMode {
 	return f.info.Mode()
 }
 
+// Seek implements File.
+func (f *LazyOsFile) Seek(offset int64, whence int) (int64, error) {
+	return f.file.Seek(offset, whence)
+}
+
 // Close implements File.
 func (f *LazyOsFile) Close(ctx context.Context) error {
 	if f.file == nil {
diff --git a/ui/build.yaml b/ui/build.yaml
index 454012b..be93902 100644
--- a/ui/build.yaml
+++ b/ui/build.yaml
@@ -11,6 +11,8 @@ targets:
               type: String
             DateTime:
               type: DateTime
+            UInt:
+              type: int
           clients:
             - graphql
             - graphql_flutter
diff --git a/ui/lib/api/schema.graphql.dart b/ui/lib/api/schema.graphql.dart
index 22a904f..6dcfa24 100644
--- a/ui/lib/api/schema.graphql.dart
+++ b/ui/lib/api/schema.graphql.dart
@@ -1033,6 +1033,282 @@ class _CopyWithStubImpl$Input$TorrentFilter<TRes>
       _res;
 }
 
+class Input$TorrentPriorityFilter {
+  factory Input$TorrentPriorityFilter({
+    Enum$TorrentPriority? eq,
+    Enum$TorrentPriority? gt,
+    Enum$TorrentPriority? lt,
+    Enum$TorrentPriority? gte,
+    Enum$TorrentPriority? lte,
+    List<Enum$TorrentPriority>? $in,
+  }) =>
+      Input$TorrentPriorityFilter._({
+        if (eq != null) r'eq': eq,
+        if (gt != null) r'gt': gt,
+        if (lt != null) r'lt': lt,
+        if (gte != null) r'gte': gte,
+        if (lte != null) r'lte': lte,
+        if ($in != null) r'in': $in,
+      });
+
+  Input$TorrentPriorityFilter._(this._$data);
+
+  factory Input$TorrentPriorityFilter.fromJson(Map<String, dynamic> data) {
+    final result$data = <String, dynamic>{};
+    if (data.containsKey('eq')) {
+      final l$eq = data['eq'];
+      result$data['eq'] =
+          l$eq == null ? null : fromJson$Enum$TorrentPriority((l$eq as String));
+    }
+    if (data.containsKey('gt')) {
+      final l$gt = data['gt'];
+      result$data['gt'] =
+          l$gt == null ? null : fromJson$Enum$TorrentPriority((l$gt as String));
+    }
+    if (data.containsKey('lt')) {
+      final l$lt = data['lt'];
+      result$data['lt'] =
+          l$lt == null ? null : fromJson$Enum$TorrentPriority((l$lt as String));
+    }
+    if (data.containsKey('gte')) {
+      final l$gte = data['gte'];
+      result$data['gte'] = l$gte == null
+          ? null
+          : fromJson$Enum$TorrentPriority((l$gte as String));
+    }
+    if (data.containsKey('lte')) {
+      final l$lte = data['lte'];
+      result$data['lte'] = l$lte == null
+          ? null
+          : fromJson$Enum$TorrentPriority((l$lte as String));
+    }
+    if (data.containsKey('in')) {
+      final l$$in = data['in'];
+      result$data['in'] = (l$$in as List<dynamic>?)
+          ?.map((e) => fromJson$Enum$TorrentPriority((e as String)))
+          .toList();
+    }
+    return Input$TorrentPriorityFilter._(result$data);
+  }
+
+  Map<String, dynamic> _$data;
+
+  Enum$TorrentPriority? get eq => (_$data['eq'] as Enum$TorrentPriority?);
+
+  Enum$TorrentPriority? get gt => (_$data['gt'] as Enum$TorrentPriority?);
+
+  Enum$TorrentPriority? get lt => (_$data['lt'] as Enum$TorrentPriority?);
+
+  Enum$TorrentPriority? get gte => (_$data['gte'] as Enum$TorrentPriority?);
+
+  Enum$TorrentPriority? get lte => (_$data['lte'] as Enum$TorrentPriority?);
+
+  List<Enum$TorrentPriority>? get $in =>
+      (_$data['in'] as List<Enum$TorrentPriority>?);
+
+  Map<String, dynamic> toJson() {
+    final result$data = <String, dynamic>{};
+    if (_$data.containsKey('eq')) {
+      final l$eq = eq;
+      result$data['eq'] =
+          l$eq == null ? null : toJson$Enum$TorrentPriority(l$eq);
+    }
+    if (_$data.containsKey('gt')) {
+      final l$gt = gt;
+      result$data['gt'] =
+          l$gt == null ? null : toJson$Enum$TorrentPriority(l$gt);
+    }
+    if (_$data.containsKey('lt')) {
+      final l$lt = lt;
+      result$data['lt'] =
+          l$lt == null ? null : toJson$Enum$TorrentPriority(l$lt);
+    }
+    if (_$data.containsKey('gte')) {
+      final l$gte = gte;
+      result$data['gte'] =
+          l$gte == null ? null : toJson$Enum$TorrentPriority(l$gte);
+    }
+    if (_$data.containsKey('lte')) {
+      final l$lte = lte;
+      result$data['lte'] =
+          l$lte == null ? null : toJson$Enum$TorrentPriority(l$lte);
+    }
+    if (_$data.containsKey('in')) {
+      final l$$in = $in;
+      result$data['in'] =
+          l$$in?.map((e) => toJson$Enum$TorrentPriority(e)).toList();
+    }
+    return result$data;
+  }
+
+  CopyWith$Input$TorrentPriorityFilter<Input$TorrentPriorityFilter>
+      get copyWith => CopyWith$Input$TorrentPriorityFilter(
+            this,
+            (i) => i,
+          );
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (!(other is Input$TorrentPriorityFilter) ||
+        runtimeType != other.runtimeType) {
+      return false;
+    }
+    final l$eq = eq;
+    final lOther$eq = other.eq;
+    if (_$data.containsKey('eq') != other._$data.containsKey('eq')) {
+      return false;
+    }
+    if (l$eq != lOther$eq) {
+      return false;
+    }
+    final l$gt = gt;
+    final lOther$gt = other.gt;
+    if (_$data.containsKey('gt') != other._$data.containsKey('gt')) {
+      return false;
+    }
+    if (l$gt != lOther$gt) {
+      return false;
+    }
+    final l$lt = lt;
+    final lOther$lt = other.lt;
+    if (_$data.containsKey('lt') != other._$data.containsKey('lt')) {
+      return false;
+    }
+    if (l$lt != lOther$lt) {
+      return false;
+    }
+    final l$gte = gte;
+    final lOther$gte = other.gte;
+    if (_$data.containsKey('gte') != other._$data.containsKey('gte')) {
+      return false;
+    }
+    if (l$gte != lOther$gte) {
+      return false;
+    }
+    final l$lte = lte;
+    final lOther$lte = other.lte;
+    if (_$data.containsKey('lte') != other._$data.containsKey('lte')) {
+      return false;
+    }
+    if (l$lte != lOther$lte) {
+      return false;
+    }
+    final l$$in = $in;
+    final lOther$$in = other.$in;
+    if (_$data.containsKey('in') != other._$data.containsKey('in')) {
+      return false;
+    }
+    if (l$$in != null && lOther$$in != null) {
+      if (l$$in.length != lOther$$in.length) {
+        return false;
+      }
+      for (int i = 0; i < l$$in.length; i++) {
+        final l$$in$entry = l$$in[i];
+        final lOther$$in$entry = lOther$$in[i];
+        if (l$$in$entry != lOther$$in$entry) {
+          return false;
+        }
+      }
+    } else if (l$$in != lOther$$in) {
+      return false;
+    }
+    return true;
+  }
+
+  @override
+  int get hashCode {
+    final l$eq = eq;
+    final l$gt = gt;
+    final l$lt = lt;
+    final l$gte = gte;
+    final l$lte = lte;
+    final l$$in = $in;
+    return Object.hashAll([
+      _$data.containsKey('eq') ? l$eq : const {},
+      _$data.containsKey('gt') ? l$gt : const {},
+      _$data.containsKey('lt') ? l$lt : const {},
+      _$data.containsKey('gte') ? l$gte : const {},
+      _$data.containsKey('lte') ? l$lte : const {},
+      _$data.containsKey('in')
+          ? l$$in == null
+              ? null
+              : Object.hashAll(l$$in.map((v) => v))
+          : const {},
+    ]);
+  }
+}
+
+abstract class CopyWith$Input$TorrentPriorityFilter<TRes> {
+  factory CopyWith$Input$TorrentPriorityFilter(
+    Input$TorrentPriorityFilter instance,
+    TRes Function(Input$TorrentPriorityFilter) then,
+  ) = _CopyWithImpl$Input$TorrentPriorityFilter;
+
+  factory CopyWith$Input$TorrentPriorityFilter.stub(TRes res) =
+      _CopyWithStubImpl$Input$TorrentPriorityFilter;
+
+  TRes call({
+    Enum$TorrentPriority? eq,
+    Enum$TorrentPriority? gt,
+    Enum$TorrentPriority? lt,
+    Enum$TorrentPriority? gte,
+    Enum$TorrentPriority? lte,
+    List<Enum$TorrentPriority>? $in,
+  });
+}
+
+class _CopyWithImpl$Input$TorrentPriorityFilter<TRes>
+    implements CopyWith$Input$TorrentPriorityFilter<TRes> {
+  _CopyWithImpl$Input$TorrentPriorityFilter(
+    this._instance,
+    this._then,
+  );
+
+  final Input$TorrentPriorityFilter _instance;
+
+  final TRes Function(Input$TorrentPriorityFilter) _then;
+
+  static const _undefined = <dynamic, dynamic>{};
+
+  TRes call({
+    Object? eq = _undefined,
+    Object? gt = _undefined,
+    Object? lt = _undefined,
+    Object? gte = _undefined,
+    Object? lte = _undefined,
+    Object? $in = _undefined,
+  }) =>
+      _then(Input$TorrentPriorityFilter._({
+        ..._instance._$data,
+        if (eq != _undefined) 'eq': (eq as Enum$TorrentPriority?),
+        if (gt != _undefined) 'gt': (gt as Enum$TorrentPriority?),
+        if (lt != _undefined) 'lt': (lt as Enum$TorrentPriority?),
+        if (gte != _undefined) 'gte': (gte as Enum$TorrentPriority?),
+        if (lte != _undefined) 'lte': (lte as Enum$TorrentPriority?),
+        if ($in != _undefined) 'in': ($in as List<Enum$TorrentPriority>?),
+      }));
+}
+
+class _CopyWithStubImpl$Input$TorrentPriorityFilter<TRes>
+    implements CopyWith$Input$TorrentPriorityFilter<TRes> {
+  _CopyWithStubImpl$Input$TorrentPriorityFilter(this._res);
+
+  TRes _res;
+
+  call({
+    Enum$TorrentPriority? eq,
+    Enum$TorrentPriority? gt,
+    Enum$TorrentPriority? lt,
+    Enum$TorrentPriority? gte,
+    Enum$TorrentPriority? lte,
+    List<Enum$TorrentPriority>? $in,
+  }) =>
+      _res;
+}
+
 class Input$TorrentsFilter {
   factory Input$TorrentsFilter({
     Input$StringFilter? infohash,
@@ -1040,7 +1316,7 @@ class Input$TorrentsFilter {
     Input$IntFilter? bytesCompleted,
     Input$IntFilter? bytesMissing,
     Input$IntFilter? peersCount,
-    Input$BooleanFilter? downloading,
+    Input$TorrentPriorityFilter? priority,
   }) =>
       Input$TorrentsFilter._({
         if (infohash != null) r'infohash': infohash,
@@ -1048,7 +1324,7 @@ class Input$TorrentsFilter {
         if (bytesCompleted != null) r'bytesCompleted': bytesCompleted,
         if (bytesMissing != null) r'bytesMissing': bytesMissing,
         if (peersCount != null) r'peersCount': peersCount,
-        if (downloading != null) r'downloading': downloading,
+        if (priority != null) r'priority': priority,
       });
 
   Input$TorrentsFilter._(this._$data);
@@ -1086,12 +1362,12 @@ class Input$TorrentsFilter {
           ? null
           : Input$IntFilter.fromJson((l$peersCount as Map<String, dynamic>));
     }
-    if (data.containsKey('downloading')) {
-      final l$downloading = data['downloading'];
-      result$data['downloading'] = l$downloading == null
+    if (data.containsKey('priority')) {
+      final l$priority = data['priority'];
+      result$data['priority'] = l$priority == null
           ? null
-          : Input$BooleanFilter.fromJson(
-              (l$downloading as Map<String, dynamic>));
+          : Input$TorrentPriorityFilter.fromJson(
+              (l$priority as Map<String, dynamic>));
     }
     return Input$TorrentsFilter._(result$data);
   }
@@ -1111,8 +1387,8 @@ class Input$TorrentsFilter {
 
   Input$IntFilter? get peersCount => (_$data['peersCount'] as Input$IntFilter?);
 
-  Input$BooleanFilter? get downloading =>
-      (_$data['downloading'] as Input$BooleanFilter?);
+  Input$TorrentPriorityFilter? get priority =>
+      (_$data['priority'] as Input$TorrentPriorityFilter?);
 
   Map<String, dynamic> toJson() {
     final result$data = <String, dynamic>{};
@@ -1136,9 +1412,9 @@ class Input$TorrentsFilter {
       final l$peersCount = peersCount;
       result$data['peersCount'] = l$peersCount?.toJson();
     }
-    if (_$data.containsKey('downloading')) {
-      final l$downloading = downloading;
-      result$data['downloading'] = l$downloading?.toJson();
+    if (_$data.containsKey('priority')) {
+      final l$priority = priority;
+      result$data['priority'] = l$priority?.toJson();
     }
     return result$data;
   }
@@ -1201,13 +1477,13 @@ class Input$TorrentsFilter {
     if (l$peersCount != lOther$peersCount) {
       return false;
     }
-    final l$downloading = downloading;
-    final lOther$downloading = other.downloading;
-    if (_$data.containsKey('downloading') !=
-        other._$data.containsKey('downloading')) {
+    final l$priority = priority;
+    final lOther$priority = other.priority;
+    if (_$data.containsKey('priority') !=
+        other._$data.containsKey('priority')) {
       return false;
     }
-    if (l$downloading != lOther$downloading) {
+    if (l$priority != lOther$priority) {
       return false;
     }
     return true;
@@ -1220,14 +1496,14 @@ class Input$TorrentsFilter {
     final l$bytesCompleted = bytesCompleted;
     final l$bytesMissing = bytesMissing;
     final l$peersCount = peersCount;
-    final l$downloading = downloading;
+    final l$priority = priority;
     return Object.hashAll([
       _$data.containsKey('infohash') ? l$infohash : const {},
       _$data.containsKey('name') ? l$name : const {},
       _$data.containsKey('bytesCompleted') ? l$bytesCompleted : const {},
       _$data.containsKey('bytesMissing') ? l$bytesMissing : const {},
       _$data.containsKey('peersCount') ? l$peersCount : const {},
-      _$data.containsKey('downloading') ? l$downloading : const {},
+      _$data.containsKey('priority') ? l$priority : const {},
     ]);
   }
 }
@@ -1247,14 +1523,14 @@ abstract class CopyWith$Input$TorrentsFilter<TRes> {
     Input$IntFilter? bytesCompleted,
     Input$IntFilter? bytesMissing,
     Input$IntFilter? peersCount,
-    Input$BooleanFilter? downloading,
+    Input$TorrentPriorityFilter? priority,
   });
   CopyWith$Input$StringFilter<TRes> get infohash;
   CopyWith$Input$StringFilter<TRes> get name;
   CopyWith$Input$IntFilter<TRes> get bytesCompleted;
   CopyWith$Input$IntFilter<TRes> get bytesMissing;
   CopyWith$Input$IntFilter<TRes> get peersCount;
-  CopyWith$Input$BooleanFilter<TRes> get downloading;
+  CopyWith$Input$TorrentPriorityFilter<TRes> get priority;
 }
 
 class _CopyWithImpl$Input$TorrentsFilter<TRes>
@@ -1276,7 +1552,7 @@ class _CopyWithImpl$Input$TorrentsFilter<TRes>
     Object? bytesCompleted = _undefined,
     Object? bytesMissing = _undefined,
     Object? peersCount = _undefined,
-    Object? downloading = _undefined,
+    Object? priority = _undefined,
   }) =>
       _then(Input$TorrentsFilter._({
         ..._instance._$data,
@@ -1289,8 +1565,8 @@ class _CopyWithImpl$Input$TorrentsFilter<TRes>
           'bytesMissing': (bytesMissing as Input$IntFilter?),
         if (peersCount != _undefined)
           'peersCount': (peersCount as Input$IntFilter?),
-        if (downloading != _undefined)
-          'downloading': (downloading as Input$BooleanFilter?),
+        if (priority != _undefined)
+          'priority': (priority as Input$TorrentPriorityFilter?),
       }));
 
   CopyWith$Input$StringFilter<TRes> get infohash {
@@ -1331,12 +1607,12 @@ class _CopyWithImpl$Input$TorrentsFilter<TRes>
             local$peersCount, (e) => call(peersCount: e));
   }
 
-  CopyWith$Input$BooleanFilter<TRes> get downloading {
-    final local$downloading = _instance.downloading;
-    return local$downloading == null
-        ? CopyWith$Input$BooleanFilter.stub(_then(_instance))
-        : CopyWith$Input$BooleanFilter(
-            local$downloading, (e) => call(downloading: e));
+  CopyWith$Input$TorrentPriorityFilter<TRes> get priority {
+    final local$priority = _instance.priority;
+    return local$priority == null
+        ? CopyWith$Input$TorrentPriorityFilter.stub(_then(_instance))
+        : CopyWith$Input$TorrentPriorityFilter(
+            local$priority, (e) => call(priority: e));
   }
 }
 
@@ -1352,7 +1628,7 @@ class _CopyWithStubImpl$Input$TorrentsFilter<TRes>
     Input$IntFilter? bytesCompleted,
     Input$IntFilter? bytesMissing,
     Input$IntFilter? peersCount,
-    Input$BooleanFilter? downloading,
+    Input$TorrentPriorityFilter? priority,
   }) =>
       _res;
 
@@ -1371,8 +1647,56 @@ class _CopyWithStubImpl$Input$TorrentsFilter<TRes>
   CopyWith$Input$IntFilter<TRes> get peersCount =>
       CopyWith$Input$IntFilter.stub(_res);
 
-  CopyWith$Input$BooleanFilter<TRes> get downloading =>
-      CopyWith$Input$BooleanFilter.stub(_res);
+  CopyWith$Input$TorrentPriorityFilter<TRes> get priority =>
+      CopyWith$Input$TorrentPriorityFilter.stub(_res);
+}
+
+enum Enum$TorrentPriority {
+  NONE,
+  NORMAL,
+  HIGH,
+  READAHEAD,
+  NOW,
+  $unknown;
+
+  factory Enum$TorrentPriority.fromJson(String value) =>
+      fromJson$Enum$TorrentPriority(value);
+
+  String toJson() => toJson$Enum$TorrentPriority(this);
+}
+
+String toJson$Enum$TorrentPriority(Enum$TorrentPriority e) {
+  switch (e) {
+    case Enum$TorrentPriority.NONE:
+      return r'NONE';
+    case Enum$TorrentPriority.NORMAL:
+      return r'NORMAL';
+    case Enum$TorrentPriority.HIGH:
+      return r'HIGH';
+    case Enum$TorrentPriority.READAHEAD:
+      return r'READAHEAD';
+    case Enum$TorrentPriority.NOW:
+      return r'NOW';
+    case Enum$TorrentPriority.$unknown:
+      return r'$unknown';
+  }
+}
+
+Enum$TorrentPriority fromJson$Enum$TorrentPriority(String value) {
+  switch (value) {
+    case r'NONE':
+      return Enum$TorrentPriority.NONE;
+    case r'NORMAL':
+      return Enum$TorrentPriority.NORMAL;
+    case r'HIGH':
+      return Enum$TorrentPriority.HIGH;
+    case r'READAHEAD':
+      return Enum$TorrentPriority.READAHEAD;
+    case r'NOW':
+      return Enum$TorrentPriority.NOW;
+    default:
+      return Enum$TorrentPriority.$unknown;
+  }
 }
 
 enum Enum$__TypeKind {
diff --git a/ui/lib/api/torrent.graphql b/ui/lib/api/torrent.graphql
index b8ef961..7a33b42 100644
--- a/ui/lib/api/torrent.graphql
+++ b/ui/lib/api/torrent.graphql
@@ -1,25 +1,21 @@
-mutation MarkTorrentDownload($infohash: String!) {
-    downloadTorrent(infohash: $infohash) {
-        task {
-            id
-        }
+mutation MarkTorrentDownload($infohash: String!, $priority: TorrentPriority! = NORMAL) {
+    torrentDaemon {
+        setTorrentPriority(infohash: $infohash, priority: $priority)
     }
 }
 
-query ListTorrents($downloading: Boolean) {
-    torrents(filter: {
-        downloading: {
-            eq: $downloading
-        }
-    }) {
-        name
-        infohash
-        bytesCompleted
-        bytesMissing
-        peers {
-            ip
-            downloadRate
-            clientName
+query ListTorrents {
+    torrentDaemon {
+        torrents {
+            name
+            infohash
+            bytesCompleted
+            bytesMissing
+            peers {
+                ip
+                downloadRate
+                clientName
+            }
         }
     }
 }
\ No newline at end of file
diff --git a/ui/lib/api/torrent.graphql.dart b/ui/lib/api/torrent.graphql.dart
index 79c2263..ee5e50a 100644
--- a/ui/lib/api/torrent.graphql.dart
+++ b/ui/lib/api/torrent.graphql.dart
@@ -4,11 +4,16 @@ import 'package:flutter/widgets.dart' as widgets;
 import 'package:gql/ast.dart';
 import 'package:graphql/client.dart' as graphql;
 import 'package:graphql_flutter/graphql_flutter.dart' as graphql_flutter;
+import 'schema.graphql.dart';
 
 class Variables$Mutation$MarkTorrentDownload {
-  factory Variables$Mutation$MarkTorrentDownload({required String infohash}) =>
+  factory Variables$Mutation$MarkTorrentDownload({
+    required String infohash,
+    Enum$TorrentPriority? priority,
+  }) =>
       Variables$Mutation$MarkTorrentDownload._({
         r'infohash': infohash,
+        if (priority != null) r'priority': priority,
       });
 
   Variables$Mutation$MarkTorrentDownload._(this._$data);
@@ -18,6 +23,11 @@ class Variables$Mutation$MarkTorrentDownload {
     final result$data = <String, dynamic>{};
     final l$infohash = data['infohash'];
     result$data['infohash'] = (l$infohash as String);
+    if (data.containsKey('priority')) {
+      final l$priority = data['priority'];
+      result$data['priority'] =
+          fromJson$Enum$TorrentPriority((l$priority as String));
+    }
     return Variables$Mutation$MarkTorrentDownload._(result$data);
   }
 
@@ -25,10 +35,18 @@ class Variables$Mutation$MarkTorrentDownload {
 
   String get infohash => (_$data['infohash'] as String);
 
+  Enum$TorrentPriority? get priority =>
+      (_$data['priority'] as Enum$TorrentPriority?);
+
   Map<String, dynamic> toJson() {
     final result$data = <String, dynamic>{};
     final l$infohash = infohash;
     result$data['infohash'] = l$infohash;
+    if (_$data.containsKey('priority')) {
+      final l$priority = priority;
+      result$data['priority'] =
+          toJson$Enum$TorrentPriority((l$priority as Enum$TorrentPriority));
+    }
     return result$data;
   }
 
@@ -53,13 +71,26 @@ class Variables$Mutation$MarkTorrentDownload {
     if (l$infohash != lOther$infohash) {
       return false;
     }
+    final l$priority = priority;
+    final lOther$priority = other.priority;
+    if (_$data.containsKey('priority') !=
+        other._$data.containsKey('priority')) {
+      return false;
+    }
+    if (l$priority != lOther$priority) {
+      return false;
+    }
     return true;
   }
 
   @override
   int get hashCode {
     final l$infohash = infohash;
-    return Object.hashAll([l$infohash]);
+    final l$priority = priority;
+    return Object.hashAll([
+      l$infohash,
+      _$data.containsKey('priority') ? l$priority : const {},
+    ]);
   }
 }
 
@@ -72,7 +103,10 @@ abstract class CopyWith$Variables$Mutation$MarkTorrentDownload<TRes> {
   factory CopyWith$Variables$Mutation$MarkTorrentDownload.stub(TRes res) =
       _CopyWithStubImpl$Variables$Mutation$MarkTorrentDownload;
 
-  TRes call({String? infohash});
+  TRes call({
+    String? infohash,
+    Enum$TorrentPriority? priority,
+  });
 }
 
 class _CopyWithImpl$Variables$Mutation$MarkTorrentDownload<TRes>
@@ -88,11 +122,16 @@ class _CopyWithImpl$Variables$Mutation$MarkTorrentDownload<TRes>
 
   static const _undefined = <dynamic, dynamic>{};
 
-  TRes call({Object? infohash = _undefined}) =>
+  TRes call({
+    Object? infohash = _undefined,
+    Object? priority = _undefined,
+  }) =>
       _then(Variables$Mutation$MarkTorrentDownload._({
         ..._instance._$data,
         if (infohash != _undefined && infohash != null)
           'infohash': (infohash as String),
+        if (priority != _undefined && priority != null)
+          'priority': (priority as Enum$TorrentPriority),
       }));
 }
 
@@ -102,35 +141,39 @@ class _CopyWithStubImpl$Variables$Mutation$MarkTorrentDownload<TRes>
 
   TRes _res;
 
-  call({String? infohash}) => _res;
+  call({
+    String? infohash,
+    Enum$TorrentPriority? priority,
+  }) =>
+      _res;
 }
 
 class Mutation$MarkTorrentDownload {
   Mutation$MarkTorrentDownload({
-    this.downloadTorrent,
+    this.torrentDaemon,
     this.$__typename = 'Mutation',
   });
 
   factory Mutation$MarkTorrentDownload.fromJson(Map<String, dynamic> json) {
-    final l$downloadTorrent = json['downloadTorrent'];
+    final l$torrentDaemon = json['torrentDaemon'];
     final l$$__typename = json['__typename'];
     return Mutation$MarkTorrentDownload(
-      downloadTorrent: l$downloadTorrent == null
+      torrentDaemon: l$torrentDaemon == null
           ? null
-          : Mutation$MarkTorrentDownload$downloadTorrent.fromJson(
-              (l$downloadTorrent as Map<String, dynamic>)),
+          : Mutation$MarkTorrentDownload$torrentDaemon.fromJson(
+              (l$torrentDaemon as Map<String, dynamic>)),
       $__typename: (l$$__typename as String),
     );
   }
 
-  final Mutation$MarkTorrentDownload$downloadTorrent? downloadTorrent;
+  final Mutation$MarkTorrentDownload$torrentDaemon? torrentDaemon;
 
   final String $__typename;
 
   Map<String, dynamic> toJson() {
     final _resultData = <String, dynamic>{};
-    final l$downloadTorrent = downloadTorrent;
-    _resultData['downloadTorrent'] = l$downloadTorrent?.toJson();
+    final l$torrentDaemon = torrentDaemon;
+    _resultData['torrentDaemon'] = l$torrentDaemon?.toJson();
     final l$$__typename = $__typename;
     _resultData['__typename'] = l$$__typename;
     return _resultData;
@@ -138,10 +181,10 @@ class Mutation$MarkTorrentDownload {
 
   @override
   int get hashCode {
-    final l$downloadTorrent = downloadTorrent;
+    final l$torrentDaemon = torrentDaemon;
     final l$$__typename = $__typename;
     return Object.hashAll([
-      l$downloadTorrent,
+      l$torrentDaemon,
       l$$__typename,
     ]);
   }
@@ -155,9 +198,9 @@ class Mutation$MarkTorrentDownload {
         runtimeType != other.runtimeType) {
       return false;
     }
-    final l$downloadTorrent = downloadTorrent;
-    final lOther$downloadTorrent = other.downloadTorrent;
-    if (l$downloadTorrent != lOther$downloadTorrent) {
+    final l$torrentDaemon = torrentDaemon;
+    final lOther$torrentDaemon = other.torrentDaemon;
+    if (l$torrentDaemon != lOther$torrentDaemon) {
       return false;
     }
     final l$$__typename = $__typename;
@@ -188,11 +231,10 @@ abstract class CopyWith$Mutation$MarkTorrentDownload<TRes> {
       _CopyWithStubImpl$Mutation$MarkTorrentDownload;
 
   TRes call({
-    Mutation$MarkTorrentDownload$downloadTorrent? downloadTorrent,
+    Mutation$MarkTorrentDownload$torrentDaemon? torrentDaemon,
     String? $__typename,
   });
-  CopyWith$Mutation$MarkTorrentDownload$downloadTorrent<TRes>
-      get downloadTorrent;
+  CopyWith$Mutation$MarkTorrentDownload$torrentDaemon<TRes> get torrentDaemon;
 }
 
 class _CopyWithImpl$Mutation$MarkTorrentDownload<TRes>
@@ -209,27 +251,25 @@ class _CopyWithImpl$Mutation$MarkTorrentDownload<TRes>
   static const _undefined = <dynamic, dynamic>{};
 
   TRes call({
-    Object? downloadTorrent = _undefined,
+    Object? torrentDaemon = _undefined,
     Object? $__typename = _undefined,
   }) =>
       _then(Mutation$MarkTorrentDownload(
-        downloadTorrent: downloadTorrent == _undefined
-            ? _instance.downloadTorrent
-            : (downloadTorrent
-                as Mutation$MarkTorrentDownload$downloadTorrent?),
+        torrentDaemon: torrentDaemon == _undefined
+            ? _instance.torrentDaemon
+            : (torrentDaemon as Mutation$MarkTorrentDownload$torrentDaemon?),
         $__typename: $__typename == _undefined || $__typename == null
             ? _instance.$__typename
             : ($__typename as String),
       ));
 
-  CopyWith$Mutation$MarkTorrentDownload$downloadTorrent<TRes>
-      get downloadTorrent {
-    final local$downloadTorrent = _instance.downloadTorrent;
-    return local$downloadTorrent == null
-        ? CopyWith$Mutation$MarkTorrentDownload$downloadTorrent.stub(
+  CopyWith$Mutation$MarkTorrentDownload$torrentDaemon<TRes> get torrentDaemon {
+    final local$torrentDaemon = _instance.torrentDaemon;
+    return local$torrentDaemon == null
+        ? CopyWith$Mutation$MarkTorrentDownload$torrentDaemon.stub(
             _then(_instance))
-        : CopyWith$Mutation$MarkTorrentDownload$downloadTorrent(
-            local$downloadTorrent, (e) => call(downloadTorrent: e));
+        : CopyWith$Mutation$MarkTorrentDownload$torrentDaemon(
+            local$torrentDaemon, (e) => call(torrentDaemon: e));
   }
 }
 
@@ -240,14 +280,13 @@ class _CopyWithStubImpl$Mutation$MarkTorrentDownload<TRes>
   TRes _res;
 
   call({
-    Mutation$MarkTorrentDownload$downloadTorrent? downloadTorrent,
+    Mutation$MarkTorrentDownload$torrentDaemon? torrentDaemon,
     String? $__typename,
   }) =>
       _res;
 
-  CopyWith$Mutation$MarkTorrentDownload$downloadTorrent<TRes>
-      get downloadTorrent =>
-          CopyWith$Mutation$MarkTorrentDownload$downloadTorrent.stub(_res);
+  CopyWith$Mutation$MarkTorrentDownload$torrentDaemon<TRes> get torrentDaemon =>
+      CopyWith$Mutation$MarkTorrentDownload$torrentDaemon.stub(_res);
 }
 
 const documentNodeMutationMarkTorrentDownload = DocumentNode(definitions: [
@@ -263,42 +302,41 @@ const documentNodeMutationMarkTorrentDownload = DocumentNode(definitions: [
         ),
         defaultValue: DefaultValueNode(value: null),
         directives: [],
-      )
+      ),
+      VariableDefinitionNode(
+        variable: VariableNode(name: NameNode(value: 'priority')),
+        type: NamedTypeNode(
+          name: NameNode(value: 'TorrentPriority'),
+          isNonNull: true,
+        ),
+        defaultValue: DefaultValueNode(
+            value: EnumValueNode(name: NameNode(value: 'NORMAL'))),
+        directives: [],
+      ),
     ],
     directives: [],
     selectionSet: SelectionSetNode(selections: [
       FieldNode(
-        name: NameNode(value: 'downloadTorrent'),
+        name: NameNode(value: 'torrentDaemon'),
         alias: null,
-        arguments: [
-          ArgumentNode(
-            name: NameNode(value: 'infohash'),
-            value: VariableNode(name: NameNode(value: 'infohash')),
-          )
-        ],
+        arguments: [],
         directives: [],
         selectionSet: SelectionSetNode(selections: [
           FieldNode(
-            name: NameNode(value: 'task'),
+            name: NameNode(value: 'setTorrentPriority'),
             alias: null,
-            arguments: [],
+            arguments: [
+              ArgumentNode(
+                name: NameNode(value: 'infohash'),
+                value: VariableNode(name: NameNode(value: 'infohash')),
+              ),
+              ArgumentNode(
+                name: NameNode(value: 'priority'),
+                value: VariableNode(name: NameNode(value: 'priority')),
+              ),
+            ],
             directives: [],
-            selectionSet: SelectionSetNode(selections: [
-              FieldNode(
-                name: NameNode(value: 'id'),
-                alias: null,
-                arguments: [],
-                directives: [],
-                selectionSet: null,
-              ),
-              FieldNode(
-                name: NameNode(value: '__typename'),
-                alias: null,
-                arguments: [],
-                directives: [],
-                selectionSet: null,
-              ),
-            ]),
+            selectionSet: null,
           ),
           FieldNode(
             name: NameNode(value: '__typename'),
@@ -536,33 +574,30 @@ class Mutation$MarkTorrentDownload$Widget
         );
 }
 
-class Mutation$MarkTorrentDownload$downloadTorrent {
-  Mutation$MarkTorrentDownload$downloadTorrent({
-    this.task,
-    this.$__typename = 'DownloadTorrentResponse',
+class Mutation$MarkTorrentDownload$torrentDaemon {
+  Mutation$MarkTorrentDownload$torrentDaemon({
+    required this.setTorrentPriority,
+    this.$__typename = 'TorrentDaemonMutation',
   });
 
-  factory Mutation$MarkTorrentDownload$downloadTorrent.fromJson(
+  factory Mutation$MarkTorrentDownload$torrentDaemon.fromJson(
       Map<String, dynamic> json) {
-    final l$task = json['task'];
+    final l$setTorrentPriority = json['setTorrentPriority'];
     final l$$__typename = json['__typename'];
-    return Mutation$MarkTorrentDownload$downloadTorrent(
-      task: l$task == null
-          ? null
-          : Mutation$MarkTorrentDownload$downloadTorrent$task.fromJson(
-              (l$task as Map<String, dynamic>)),
+    return Mutation$MarkTorrentDownload$torrentDaemon(
+      setTorrentPriority: (l$setTorrentPriority as bool),
       $__typename: (l$$__typename as String),
     );
   }
 
-  final Mutation$MarkTorrentDownload$downloadTorrent$task? task;
+  final bool setTorrentPriority;
 
   final String $__typename;
 
   Map<String, dynamic> toJson() {
     final _resultData = <String, dynamic>{};
-    final l$task = task;
-    _resultData['task'] = l$task?.toJson();
+    final l$setTorrentPriority = setTorrentPriority;
+    _resultData['setTorrentPriority'] = l$setTorrentPriority;
     final l$$__typename = $__typename;
     _resultData['__typename'] = l$$__typename;
     return _resultData;
@@ -570,10 +605,10 @@ class Mutation$MarkTorrentDownload$downloadTorrent {
 
   @override
   int get hashCode {
-    final l$task = task;
+    final l$setTorrentPriority = setTorrentPriority;
     final l$$__typename = $__typename;
     return Object.hashAll([
-      l$task,
+      l$setTorrentPriority,
       l$$__typename,
     ]);
   }
@@ -583,13 +618,13 @@ class Mutation$MarkTorrentDownload$downloadTorrent {
     if (identical(this, other)) {
       return true;
     }
-    if (!(other is Mutation$MarkTorrentDownload$downloadTorrent) ||
+    if (!(other is Mutation$MarkTorrentDownload$torrentDaemon) ||
         runtimeType != other.runtimeType) {
       return false;
     }
-    final l$task = task;
-    final lOther$task = other.task;
-    if (l$task != lOther$task) {
+    final l$setTorrentPriority = setTorrentPriority;
+    final lOther$setTorrentPriority = other.setTorrentPriority;
+    if (l$setTorrentPriority != lOther$setTorrentPriority) {
       return false;
     }
     final l$$__typename = $__typename;
@@ -601,347 +636,98 @@ class Mutation$MarkTorrentDownload$downloadTorrent {
   }
 }
 
-extension UtilityExtension$Mutation$MarkTorrentDownload$downloadTorrent
-    on Mutation$MarkTorrentDownload$downloadTorrent {
-  CopyWith$Mutation$MarkTorrentDownload$downloadTorrent<
-          Mutation$MarkTorrentDownload$downloadTorrent>
-      get copyWith => CopyWith$Mutation$MarkTorrentDownload$downloadTorrent(
+extension UtilityExtension$Mutation$MarkTorrentDownload$torrentDaemon
+    on Mutation$MarkTorrentDownload$torrentDaemon {
+  CopyWith$Mutation$MarkTorrentDownload$torrentDaemon<
+          Mutation$MarkTorrentDownload$torrentDaemon>
+      get copyWith => CopyWith$Mutation$MarkTorrentDownload$torrentDaemon(
             this,
             (i) => i,
           );
 }
 
-abstract class CopyWith$Mutation$MarkTorrentDownload$downloadTorrent<TRes> {
-  factory CopyWith$Mutation$MarkTorrentDownload$downloadTorrent(
-    Mutation$MarkTorrentDownload$downloadTorrent instance,
-    TRes Function(Mutation$MarkTorrentDownload$downloadTorrent) then,
-  ) = _CopyWithImpl$Mutation$MarkTorrentDownload$downloadTorrent;
+abstract class CopyWith$Mutation$MarkTorrentDownload$torrentDaemon<TRes> {
+  factory CopyWith$Mutation$MarkTorrentDownload$torrentDaemon(
+    Mutation$MarkTorrentDownload$torrentDaemon instance,
+    TRes Function(Mutation$MarkTorrentDownload$torrentDaemon) then,
+  ) = _CopyWithImpl$Mutation$MarkTorrentDownload$torrentDaemon;
 
-  factory CopyWith$Mutation$MarkTorrentDownload$downloadTorrent.stub(TRes res) =
-      _CopyWithStubImpl$Mutation$MarkTorrentDownload$downloadTorrent;
+  factory CopyWith$Mutation$MarkTorrentDownload$torrentDaemon.stub(TRes res) =
+      _CopyWithStubImpl$Mutation$MarkTorrentDownload$torrentDaemon;
 
   TRes call({
-    Mutation$MarkTorrentDownload$downloadTorrent$task? task,
+    bool? setTorrentPriority,
     String? $__typename,
   });
-  CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task<TRes> get task;
 }
 
-class _CopyWithImpl$Mutation$MarkTorrentDownload$downloadTorrent<TRes>
-    implements CopyWith$Mutation$MarkTorrentDownload$downloadTorrent<TRes> {
-  _CopyWithImpl$Mutation$MarkTorrentDownload$downloadTorrent(
+class _CopyWithImpl$Mutation$MarkTorrentDownload$torrentDaemon<TRes>
+    implements CopyWith$Mutation$MarkTorrentDownload$torrentDaemon<TRes> {
+  _CopyWithImpl$Mutation$MarkTorrentDownload$torrentDaemon(
     this._instance,
     this._then,
   );
 
-  final Mutation$MarkTorrentDownload$downloadTorrent _instance;
+  final Mutation$MarkTorrentDownload$torrentDaemon _instance;
 
-  final TRes Function(Mutation$MarkTorrentDownload$downloadTorrent) _then;
+  final TRes Function(Mutation$MarkTorrentDownload$torrentDaemon) _then;
 
   static const _undefined = <dynamic, dynamic>{};
 
   TRes call({
-    Object? task = _undefined,
+    Object? setTorrentPriority = _undefined,
     Object? $__typename = _undefined,
   }) =>
-      _then(Mutation$MarkTorrentDownload$downloadTorrent(
-        task: task == _undefined
-            ? _instance.task
-            : (task as Mutation$MarkTorrentDownload$downloadTorrent$task?),
-        $__typename: $__typename == _undefined || $__typename == null
-            ? _instance.$__typename
-            : ($__typename as String),
-      ));
-
-  CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task<TRes> get task {
-    final local$task = _instance.task;
-    return local$task == null
-        ? CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task.stub(
-            _then(_instance))
-        : CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task(
-            local$task, (e) => call(task: e));
-  }
-}
-
-class _CopyWithStubImpl$Mutation$MarkTorrentDownload$downloadTorrent<TRes>
-    implements CopyWith$Mutation$MarkTorrentDownload$downloadTorrent<TRes> {
-  _CopyWithStubImpl$Mutation$MarkTorrentDownload$downloadTorrent(this._res);
-
-  TRes _res;
-
-  call({
-    Mutation$MarkTorrentDownload$downloadTorrent$task? task,
-    String? $__typename,
-  }) =>
-      _res;
-
-  CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task<TRes> get task =>
-      CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task.stub(_res);
-}
-
-class Mutation$MarkTorrentDownload$downloadTorrent$task {
-  Mutation$MarkTorrentDownload$downloadTorrent$task({
-    required this.id,
-    this.$__typename = 'Task',
-  });
-
-  factory Mutation$MarkTorrentDownload$downloadTorrent$task.fromJson(
-      Map<String, dynamic> json) {
-    final l$id = json['id'];
-    final l$$__typename = json['__typename'];
-    return Mutation$MarkTorrentDownload$downloadTorrent$task(
-      id: (l$id as String),
-      $__typename: (l$$__typename as String),
-    );
-  }
-
-  final String id;
-
-  final String $__typename;
-
-  Map<String, dynamic> toJson() {
-    final _resultData = <String, dynamic>{};
-    final l$id = id;
-    _resultData['id'] = l$id;
-    final l$$__typename = $__typename;
-    _resultData['__typename'] = l$$__typename;
-    return _resultData;
-  }
-
-  @override
-  int get hashCode {
-    final l$id = id;
-    final l$$__typename = $__typename;
-    return Object.hashAll([
-      l$id,
-      l$$__typename,
-    ]);
-  }
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) {
-      return true;
-    }
-    if (!(other is Mutation$MarkTorrentDownload$downloadTorrent$task) ||
-        runtimeType != other.runtimeType) {
-      return false;
-    }
-    final l$id = id;
-    final lOther$id = other.id;
-    if (l$id != lOther$id) {
-      return false;
-    }
-    final l$$__typename = $__typename;
-    final lOther$$__typename = other.$__typename;
-    if (l$$__typename != lOther$$__typename) {
-      return false;
-    }
-    return true;
-  }
-}
-
-extension UtilityExtension$Mutation$MarkTorrentDownload$downloadTorrent$task
-    on Mutation$MarkTorrentDownload$downloadTorrent$task {
-  CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task<
-          Mutation$MarkTorrentDownload$downloadTorrent$task>
-      get copyWith =>
-          CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task(
-            this,
-            (i) => i,
-          );
-}
-
-abstract class CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task<
-    TRes> {
-  factory CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task(
-    Mutation$MarkTorrentDownload$downloadTorrent$task instance,
-    TRes Function(Mutation$MarkTorrentDownload$downloadTorrent$task) then,
-  ) = _CopyWithImpl$Mutation$MarkTorrentDownload$downloadTorrent$task;
-
-  factory CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task.stub(
-          TRes res) =
-      _CopyWithStubImpl$Mutation$MarkTorrentDownload$downloadTorrent$task;
-
-  TRes call({
-    String? id,
-    String? $__typename,
-  });
-}
-
-class _CopyWithImpl$Mutation$MarkTorrentDownload$downloadTorrent$task<TRes>
-    implements
-        CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task<TRes> {
-  _CopyWithImpl$Mutation$MarkTorrentDownload$downloadTorrent$task(
-    this._instance,
-    this._then,
-  );
-
-  final Mutation$MarkTorrentDownload$downloadTorrent$task _instance;
-
-  final TRes Function(Mutation$MarkTorrentDownload$downloadTorrent$task) _then;
-
-  static const _undefined = <dynamic, dynamic>{};
-
-  TRes call({
-    Object? id = _undefined,
-    Object? $__typename = _undefined,
-  }) =>
-      _then(Mutation$MarkTorrentDownload$downloadTorrent$task(
-        id: id == _undefined || id == null ? _instance.id : (id as String),
+      _then(Mutation$MarkTorrentDownload$torrentDaemon(
+        setTorrentPriority:
+            setTorrentPriority == _undefined || setTorrentPriority == null
+                ? _instance.setTorrentPriority
+                : (setTorrentPriority as bool),
         $__typename: $__typename == _undefined || $__typename == null
             ? _instance.$__typename
             : ($__typename as String),
       ));
 }
 
-class _CopyWithStubImpl$Mutation$MarkTorrentDownload$downloadTorrent$task<TRes>
-    implements
-        CopyWith$Mutation$MarkTorrentDownload$downloadTorrent$task<TRes> {
-  _CopyWithStubImpl$Mutation$MarkTorrentDownload$downloadTorrent$task(
-      this._res);
+class _CopyWithStubImpl$Mutation$MarkTorrentDownload$torrentDaemon<TRes>
+    implements CopyWith$Mutation$MarkTorrentDownload$torrentDaemon<TRes> {
+  _CopyWithStubImpl$Mutation$MarkTorrentDownload$torrentDaemon(this._res);
 
   TRes _res;
 
   call({
-    String? id,
+    bool? setTorrentPriority,
     String? $__typename,
   }) =>
       _res;
 }
 
-class Variables$Query$ListTorrents {
-  factory Variables$Query$ListTorrents({bool? downloading}) =>
-      Variables$Query$ListTorrents._({
-        if (downloading != null) r'downloading': downloading,
-      });
-
-  Variables$Query$ListTorrents._(this._$data);
-
-  factory Variables$Query$ListTorrents.fromJson(Map<String, dynamic> data) {
-    final result$data = <String, dynamic>{};
-    if (data.containsKey('downloading')) {
-      final l$downloading = data['downloading'];
-      result$data['downloading'] = (l$downloading as bool?);
-    }
-    return Variables$Query$ListTorrents._(result$data);
-  }
-
-  Map<String, dynamic> _$data;
-
-  bool? get downloading => (_$data['downloading'] as bool?);
-
-  Map<String, dynamic> toJson() {
-    final result$data = <String, dynamic>{};
-    if (_$data.containsKey('downloading')) {
-      final l$downloading = downloading;
-      result$data['downloading'] = l$downloading;
-    }
-    return result$data;
-  }
-
-  CopyWith$Variables$Query$ListTorrents<Variables$Query$ListTorrents>
-      get copyWith => CopyWith$Variables$Query$ListTorrents(
-            this,
-            (i) => i,
-          );
-
-  @override
-  bool operator ==(Object other) {
-    if (identical(this, other)) {
-      return true;
-    }
-    if (!(other is Variables$Query$ListTorrents) ||
-        runtimeType != other.runtimeType) {
-      return false;
-    }
-    final l$downloading = downloading;
-    final lOther$downloading = other.downloading;
-    if (_$data.containsKey('downloading') !=
-        other._$data.containsKey('downloading')) {
-      return false;
-    }
-    if (l$downloading != lOther$downloading) {
-      return false;
-    }
-    return true;
-  }
-
-  @override
-  int get hashCode {
-    final l$downloading = downloading;
-    return Object.hashAll(
-        [_$data.containsKey('downloading') ? l$downloading : const {}]);
-  }
-}
-
-abstract class CopyWith$Variables$Query$ListTorrents<TRes> {
-  factory CopyWith$Variables$Query$ListTorrents(
-    Variables$Query$ListTorrents instance,
-    TRes Function(Variables$Query$ListTorrents) then,
-  ) = _CopyWithImpl$Variables$Query$ListTorrents;
-
-  factory CopyWith$Variables$Query$ListTorrents.stub(TRes res) =
-      _CopyWithStubImpl$Variables$Query$ListTorrents;
-
-  TRes call({bool? downloading});
-}
-
-class _CopyWithImpl$Variables$Query$ListTorrents<TRes>
-    implements CopyWith$Variables$Query$ListTorrents<TRes> {
-  _CopyWithImpl$Variables$Query$ListTorrents(
-    this._instance,
-    this._then,
-  );
-
-  final Variables$Query$ListTorrents _instance;
-
-  final TRes Function(Variables$Query$ListTorrents) _then;
-
-  static const _undefined = <dynamic, dynamic>{};
-
-  TRes call({Object? downloading = _undefined}) =>
-      _then(Variables$Query$ListTorrents._({
-        ..._instance._$data,
-        if (downloading != _undefined) 'downloading': (downloading as bool?),
-      }));
-}
-
-class _CopyWithStubImpl$Variables$Query$ListTorrents<TRes>
-    implements CopyWith$Variables$Query$ListTorrents<TRes> {
-  _CopyWithStubImpl$Variables$Query$ListTorrents(this._res);
-
-  TRes _res;
-
-  call({bool? downloading}) => _res;
-}
-
 class Query$ListTorrents {
   Query$ListTorrents({
-    required this.torrents,
+    this.torrentDaemon,
     this.$__typename = 'Query',
   });
 
   factory Query$ListTorrents.fromJson(Map<String, dynamic> json) {
-    final l$torrents = json['torrents'];
+    final l$torrentDaemon = json['torrentDaemon'];
     final l$$__typename = json['__typename'];
     return Query$ListTorrents(
-      torrents: (l$torrents as List<dynamic>)
-          .map((e) =>
-              Query$ListTorrents$torrents.fromJson((e as Map<String, dynamic>)))
-          .toList(),
+      torrentDaemon: l$torrentDaemon == null
+          ? null
+          : Query$ListTorrents$torrentDaemon.fromJson(
+              (l$torrentDaemon as Map<String, dynamic>)),
       $__typename: (l$$__typename as String),
     );
   }
 
-  final List<Query$ListTorrents$torrents> torrents;
+  final Query$ListTorrents$torrentDaemon? torrentDaemon;
 
   final String $__typename;
 
   Map<String, dynamic> toJson() {
     final _resultData = <String, dynamic>{};
-    final l$torrents = torrents;
-    _resultData['torrents'] = l$torrents.map((e) => e.toJson()).toList();
+    final l$torrentDaemon = torrentDaemon;
+    _resultData['torrentDaemon'] = l$torrentDaemon?.toJson();
     final l$$__typename = $__typename;
     _resultData['__typename'] = l$$__typename;
     return _resultData;
@@ -949,10 +735,10 @@ class Query$ListTorrents {
 
   @override
   int get hashCode {
-    final l$torrents = torrents;
+    final l$torrentDaemon = torrentDaemon;
     final l$$__typename = $__typename;
     return Object.hashAll([
-      Object.hashAll(l$torrents.map((v) => v)),
+      l$torrentDaemon,
       l$$__typename,
     ]);
   }
@@ -965,18 +751,11 @@ class Query$ListTorrents {
     if (!(other is Query$ListTorrents) || runtimeType != other.runtimeType) {
       return false;
     }
-    final l$torrents = torrents;
-    final lOther$torrents = other.torrents;
-    if (l$torrents.length != lOther$torrents.length) {
+    final l$torrentDaemon = torrentDaemon;
+    final lOther$torrentDaemon = other.torrentDaemon;
+    if (l$torrentDaemon != lOther$torrentDaemon) {
       return false;
     }
-    for (int i = 0; i < l$torrents.length; i++) {
-      final l$torrents$entry = l$torrents[i];
-      final lOther$torrents$entry = lOther$torrents[i];
-      if (l$torrents$entry != lOther$torrents$entry) {
-        return false;
-      }
-    }
     final l$$__typename = $__typename;
     final lOther$$__typename = other.$__typename;
     if (l$$__typename != lOther$$__typename) {
@@ -1004,15 +783,10 @@ abstract class CopyWith$Query$ListTorrents<TRes> {
       _CopyWithStubImpl$Query$ListTorrents;
 
   TRes call({
-    List<Query$ListTorrents$torrents>? torrents,
+    Query$ListTorrents$torrentDaemon? torrentDaemon,
     String? $__typename,
   });
-  TRes torrents(
-      Iterable<Query$ListTorrents$torrents> Function(
-              Iterable<
-                  CopyWith$Query$ListTorrents$torrents<
-                      Query$ListTorrents$torrents>>)
-          _fn);
+  CopyWith$Query$ListTorrents$torrentDaemon<TRes> get torrentDaemon;
 }
 
 class _CopyWithImpl$Query$ListTorrents<TRes>
@@ -1029,30 +803,25 @@ class _CopyWithImpl$Query$ListTorrents<TRes>
   static const _undefined = <dynamic, dynamic>{};
 
   TRes call({
-    Object? torrents = _undefined,
+    Object? torrentDaemon = _undefined,
     Object? $__typename = _undefined,
   }) =>
       _then(Query$ListTorrents(
-        torrents: torrents == _undefined || torrents == null
-            ? _instance.torrents
-            : (torrents as List<Query$ListTorrents$torrents>),
+        torrentDaemon: torrentDaemon == _undefined
+            ? _instance.torrentDaemon
+            : (torrentDaemon as Query$ListTorrents$torrentDaemon?),
         $__typename: $__typename == _undefined || $__typename == null
             ? _instance.$__typename
             : ($__typename as String),
       ));
 
-  TRes torrents(
-          Iterable<Query$ListTorrents$torrents> Function(
-                  Iterable<
-                      CopyWith$Query$ListTorrents$torrents<
-                          Query$ListTorrents$torrents>>)
-              _fn) =>
-      call(
-          torrents: _fn(_instance.torrents
-              .map((e) => CopyWith$Query$ListTorrents$torrents(
-                    e,
-                    (i) => i,
-                  ))).toList());
+  CopyWith$Query$ListTorrents$torrentDaemon<TRes> get torrentDaemon {
+    final local$torrentDaemon = _instance.torrentDaemon;
+    return local$torrentDaemon == null
+        ? CopyWith$Query$ListTorrents$torrentDaemon.stub(_then(_instance))
+        : CopyWith$Query$ListTorrents$torrentDaemon(
+            local$torrentDaemon, (e) => call(torrentDaemon: e));
+  }
 }
 
 class _CopyWithStubImpl$Query$ListTorrents<TRes>
@@ -1062,107 +831,98 @@ class _CopyWithStubImpl$Query$ListTorrents<TRes>
   TRes _res;
 
   call({
-    List<Query$ListTorrents$torrents>? torrents,
+    Query$ListTorrents$torrentDaemon? torrentDaemon,
     String? $__typename,
   }) =>
       _res;
 
-  torrents(_fn) => _res;
+  CopyWith$Query$ListTorrents$torrentDaemon<TRes> get torrentDaemon =>
+      CopyWith$Query$ListTorrents$torrentDaemon.stub(_res);
 }
 
 const documentNodeQueryListTorrents = DocumentNode(definitions: [
   OperationDefinitionNode(
     type: OperationType.query,
     name: NameNode(value: 'ListTorrents'),
-    variableDefinitions: [
-      VariableDefinitionNode(
-        variable: VariableNode(name: NameNode(value: 'downloading')),
-        type: NamedTypeNode(
-          name: NameNode(value: 'Boolean'),
-          isNonNull: false,
-        ),
-        defaultValue: DefaultValueNode(value: null),
-        directives: [],
-      )
-    ],
+    variableDefinitions: [],
     directives: [],
     selectionSet: SelectionSetNode(selections: [
       FieldNode(
-        name: NameNode(value: 'torrents'),
+        name: NameNode(value: 'torrentDaemon'),
         alias: null,
-        arguments: [
-          ArgumentNode(
-            name: NameNode(value: 'filter'),
-            value: ObjectValueNode(fields: [
-              ObjectFieldNode(
-                name: NameNode(value: 'downloading'),
-                value: ObjectValueNode(fields: [
-                  ObjectFieldNode(
-                    name: NameNode(value: 'eq'),
-                    value: VariableNode(name: NameNode(value: 'downloading')),
-                  )
-                ]),
-              )
-            ]),
-          )
-        ],
+        arguments: [],
         directives: [],
         selectionSet: SelectionSetNode(selections: [
           FieldNode(
-            name: NameNode(value: 'name'),
-            alias: null,
-            arguments: [],
-            directives: [],
-            selectionSet: null,
-          ),
-          FieldNode(
-            name: NameNode(value: 'infohash'),
-            alias: null,
-            arguments: [],
-            directives: [],
-            selectionSet: null,
-          ),
-          FieldNode(
-            name: NameNode(value: 'bytesCompleted'),
-            alias: null,
-            arguments: [],
-            directives: [],
-            selectionSet: null,
-          ),
-          FieldNode(
-            name: NameNode(value: 'bytesMissing'),
-            alias: null,
-            arguments: [],
-            directives: [],
-            selectionSet: null,
-          ),
-          FieldNode(
-            name: NameNode(value: 'peers'),
+            name: NameNode(value: 'torrents'),
             alias: null,
             arguments: [],
             directives: [],
             selectionSet: SelectionSetNode(selections: [
               FieldNode(
-                name: NameNode(value: 'ip'),
+                name: NameNode(value: 'name'),
                 alias: null,
                 arguments: [],
                 directives: [],
                 selectionSet: null,
               ),
               FieldNode(
-                name: NameNode(value: 'downloadRate'),
+                name: NameNode(value: 'infohash'),
                 alias: null,
                 arguments: [],
                 directives: [],
                 selectionSet: null,
               ),
               FieldNode(
-                name: NameNode(value: 'clientName'),
+                name: NameNode(value: 'bytesCompleted'),
                 alias: null,
                 arguments: [],
                 directives: [],
                 selectionSet: null,
               ),
+              FieldNode(
+                name: NameNode(value: 'bytesMissing'),
+                alias: null,
+                arguments: [],
+                directives: [],
+                selectionSet: null,
+              ),
+              FieldNode(
+                name: NameNode(value: 'peers'),
+                alias: null,
+                arguments: [],
+                directives: [],
+                selectionSet: SelectionSetNode(selections: [
+                  FieldNode(
+                    name: NameNode(value: 'ip'),
+                    alias: null,
+                    arguments: [],
+                    directives: [],
+                    selectionSet: null,
+                  ),
+                  FieldNode(
+                    name: NameNode(value: 'downloadRate'),
+                    alias: null,
+                    arguments: [],
+                    directives: [],
+                    selectionSet: null,
+                  ),
+                  FieldNode(
+                    name: NameNode(value: 'clientName'),
+                    alias: null,
+                    arguments: [],
+                    directives: [],
+                    selectionSet: null,
+                  ),
+                  FieldNode(
+                    name: NameNode(value: '__typename'),
+                    alias: null,
+                    arguments: [],
+                    directives: [],
+                    selectionSet: null,
+                  ),
+                ]),
+              ),
               FieldNode(
                 name: NameNode(value: '__typename'),
                 alias: null,
@@ -1202,7 +962,6 @@ class Options$Query$ListTorrents
     extends graphql.QueryOptions<Query$ListTorrents> {
   Options$Query$ListTorrents({
     String? operationName,
-    Variables$Query$ListTorrents? variables,
     graphql.FetchPolicy? fetchPolicy,
     graphql.ErrorPolicy? errorPolicy,
     graphql.CacheRereadPolicy? cacheRereadPolicy,
@@ -1214,7 +973,6 @@ class Options$Query$ListTorrents
     graphql.OnQueryError? onError,
   })  : onCompleteWithParsed = onComplete,
         super(
-          variables: variables?.toJson() ?? {},
           operationName: operationName,
           fetchPolicy: fetchPolicy,
           errorPolicy: errorPolicy,
@@ -1248,7 +1006,6 @@ class WatchOptions$Query$ListTorrents
     extends graphql.WatchQueryOptions<Query$ListTorrents> {
   WatchOptions$Query$ListTorrents({
     String? operationName,
-    Variables$Query$ListTorrents? variables,
     graphql.FetchPolicy? fetchPolicy,
     graphql.ErrorPolicy? errorPolicy,
     graphql.CacheRereadPolicy? cacheRereadPolicy,
@@ -1260,7 +1017,6 @@ class WatchOptions$Query$ListTorrents
     bool carryForwardDataOnException = true,
     bool fetchResults = false,
   }) : super(
-          variables: variables?.toJson() ?? {},
           operationName: operationName,
           fetchPolicy: fetchPolicy,
           errorPolicy: errorPolicy,
@@ -1277,12 +1033,10 @@ class WatchOptions$Query$ListTorrents
 }
 
 class FetchMoreOptions$Query$ListTorrents extends graphql.FetchMoreOptions {
-  FetchMoreOptions$Query$ListTorrents({
-    required graphql.UpdateQuery updateQuery,
-    Variables$Query$ListTorrents? variables,
-  }) : super(
+  FetchMoreOptions$Query$ListTorrents(
+      {required graphql.UpdateQuery updateQuery})
+      : super(
           updateQuery: updateQuery,
-          variables: variables?.toJson() ?? {},
           document: documentNodeQueryListTorrents,
         );
 }
@@ -1296,26 +1050,20 @@ extension ClientExtension$Query$ListTorrents on graphql.GraphQLClient {
       this.watchQuery(options ?? WatchOptions$Query$ListTorrents());
   void writeQuery$ListTorrents({
     required Query$ListTorrents data,
-    Variables$Query$ListTorrents? variables,
     bool broadcast = true,
   }) =>
       this.writeQuery(
         graphql.Request(
-          operation: graphql.Operation(document: documentNodeQueryListTorrents),
-          variables: variables?.toJson() ?? const {},
-        ),
+            operation:
+                graphql.Operation(document: documentNodeQueryListTorrents)),
         data: data.toJson(),
         broadcast: broadcast,
       );
-  Query$ListTorrents? readQuery$ListTorrents({
-    Variables$Query$ListTorrents? variables,
-    bool optimistic = true,
-  }) {
+  Query$ListTorrents? readQuery$ListTorrents({bool optimistic = true}) {
     final result = this.readQuery(
       graphql.Request(
-        operation: graphql.Operation(document: documentNodeQueryListTorrents),
-        variables: variables?.toJson() ?? const {},
-      ),
+          operation:
+              graphql.Operation(document: documentNodeQueryListTorrents)),
       optimistic: optimistic,
     );
     return result == null ? null : Query$ListTorrents.fromJson(result);
@@ -1342,8 +1090,164 @@ class Query$ListTorrents$Widget
         );
 }
 
-class Query$ListTorrents$torrents {
-  Query$ListTorrents$torrents({
+class Query$ListTorrents$torrentDaemon {
+  Query$ListTorrents$torrentDaemon({
+    required this.torrents,
+    this.$__typename = 'TorrentDaemonQuery',
+  });
+
+  factory Query$ListTorrents$torrentDaemon.fromJson(Map<String, dynamic> json) {
+    final l$torrents = json['torrents'];
+    final l$$__typename = json['__typename'];
+    return Query$ListTorrents$torrentDaemon(
+      torrents: (l$torrents as List<dynamic>)
+          .map((e) => Query$ListTorrents$torrentDaemon$torrents.fromJson(
+              (e as Map<String, dynamic>)))
+          .toList(),
+      $__typename: (l$$__typename as String),
+    );
+  }
+
+  final List<Query$ListTorrents$torrentDaemon$torrents> torrents;
+
+  final String $__typename;
+
+  Map<String, dynamic> toJson() {
+    final _resultData = <String, dynamic>{};
+    final l$torrents = torrents;
+    _resultData['torrents'] = l$torrents.map((e) => e.toJson()).toList();
+    final l$$__typename = $__typename;
+    _resultData['__typename'] = l$$__typename;
+    return _resultData;
+  }
+
+  @override
+  int get hashCode {
+    final l$torrents = torrents;
+    final l$$__typename = $__typename;
+    return Object.hashAll([
+      Object.hashAll(l$torrents.map((v) => v)),
+      l$$__typename,
+    ]);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (!(other is Query$ListTorrents$torrentDaemon) ||
+        runtimeType != other.runtimeType) {
+      return false;
+    }
+    final l$torrents = torrents;
+    final lOther$torrents = other.torrents;
+    if (l$torrents.length != lOther$torrents.length) {
+      return false;
+    }
+    for (int i = 0; i < l$torrents.length; i++) {
+      final l$torrents$entry = l$torrents[i];
+      final lOther$torrents$entry = lOther$torrents[i];
+      if (l$torrents$entry != lOther$torrents$entry) {
+        return false;
+      }
+    }
+    final l$$__typename = $__typename;
+    final lOther$$__typename = other.$__typename;
+    if (l$$__typename != lOther$$__typename) {
+      return false;
+    }
+    return true;
+  }
+}
+
+extension UtilityExtension$Query$ListTorrents$torrentDaemon
+    on Query$ListTorrents$torrentDaemon {
+  CopyWith$Query$ListTorrents$torrentDaemon<Query$ListTorrents$torrentDaemon>
+      get copyWith => CopyWith$Query$ListTorrents$torrentDaemon(
+            this,
+            (i) => i,
+          );
+}
+
+abstract class CopyWith$Query$ListTorrents$torrentDaemon<TRes> {
+  factory CopyWith$Query$ListTorrents$torrentDaemon(
+    Query$ListTorrents$torrentDaemon instance,
+    TRes Function(Query$ListTorrents$torrentDaemon) then,
+  ) = _CopyWithImpl$Query$ListTorrents$torrentDaemon;
+
+  factory CopyWith$Query$ListTorrents$torrentDaemon.stub(TRes res) =
+      _CopyWithStubImpl$Query$ListTorrents$torrentDaemon;
+
+  TRes call({
+    List<Query$ListTorrents$torrentDaemon$torrents>? torrents,
+    String? $__typename,
+  });
+  TRes torrents(
+      Iterable<Query$ListTorrents$torrentDaemon$torrents> Function(
+              Iterable<
+                  CopyWith$Query$ListTorrents$torrentDaemon$torrents<
+                      Query$ListTorrents$torrentDaemon$torrents>>)
+          _fn);
+}
+
+class _CopyWithImpl$Query$ListTorrents$torrentDaemon<TRes>
+    implements CopyWith$Query$ListTorrents$torrentDaemon<TRes> {
+  _CopyWithImpl$Query$ListTorrents$torrentDaemon(
+    this._instance,
+    this._then,
+  );
+
+  final Query$ListTorrents$torrentDaemon _instance;
+
+  final TRes Function(Query$ListTorrents$torrentDaemon) _then;
+
+  static const _undefined = <dynamic, dynamic>{};
+
+  TRes call({
+    Object? torrents = _undefined,
+    Object? $__typename = _undefined,
+  }) =>
+      _then(Query$ListTorrents$torrentDaemon(
+        torrents: torrents == _undefined || torrents == null
+            ? _instance.torrents
+            : (torrents as List<Query$ListTorrents$torrentDaemon$torrents>),
+        $__typename: $__typename == _undefined || $__typename == null
+            ? _instance.$__typename
+            : ($__typename as String),
+      ));
+
+  TRes torrents(
+          Iterable<Query$ListTorrents$torrentDaemon$torrents> Function(
+                  Iterable<
+                      CopyWith$Query$ListTorrents$torrentDaemon$torrents<
+                          Query$ListTorrents$torrentDaemon$torrents>>)
+              _fn) =>
+      call(
+          torrents: _fn(_instance.torrents
+              .map((e) => CopyWith$Query$ListTorrents$torrentDaemon$torrents(
+                    e,
+                    (i) => i,
+                  ))).toList());
+}
+
+class _CopyWithStubImpl$Query$ListTorrents$torrentDaemon<TRes>
+    implements CopyWith$Query$ListTorrents$torrentDaemon<TRes> {
+  _CopyWithStubImpl$Query$ListTorrents$torrentDaemon(this._res);
+
+  TRes _res;
+
+  call({
+    List<Query$ListTorrents$torrentDaemon$torrents>? torrents,
+    String? $__typename,
+  }) =>
+      _res;
+
+  torrents(_fn) => _res;
+}
+
+class Query$ListTorrents$torrentDaemon$torrents {
+  Query$ListTorrents$torrentDaemon$torrents({
     required this.name,
     required this.infohash,
     required this.bytesCompleted,
@@ -1352,20 +1256,21 @@ class Query$ListTorrents$torrents {
     this.$__typename = 'Torrent',
   });
 
-  factory Query$ListTorrents$torrents.fromJson(Map<String, dynamic> json) {
+  factory Query$ListTorrents$torrentDaemon$torrents.fromJson(
+      Map<String, dynamic> json) {
     final l$name = json['name'];
     final l$infohash = json['infohash'];
     final l$bytesCompleted = json['bytesCompleted'];
     final l$bytesMissing = json['bytesMissing'];
     final l$peers = json['peers'];
     final l$$__typename = json['__typename'];
-    return Query$ListTorrents$torrents(
+    return Query$ListTorrents$torrentDaemon$torrents(
       name: (l$name as String),
       infohash: (l$infohash as String),
       bytesCompleted: (l$bytesCompleted as int),
       bytesMissing: (l$bytesMissing as int),
       peers: (l$peers as List<dynamic>)
-          .map((e) => Query$ListTorrents$torrents$peers.fromJson(
+          .map((e) => Query$ListTorrents$torrentDaemon$torrents$peers.fromJson(
               (e as Map<String, dynamic>)))
           .toList(),
       $__typename: (l$$__typename as String),
@@ -1380,7 +1285,7 @@ class Query$ListTorrents$torrents {
 
   final int bytesMissing;
 
-  final List<Query$ListTorrents$torrents$peers> peers;
+  final List<Query$ListTorrents$torrentDaemon$torrents$peers> peers;
 
   final String $__typename;
 
@@ -1424,7 +1329,7 @@ class Query$ListTorrents$torrents {
     if (identical(this, other)) {
       return true;
     }
-    if (!(other is Query$ListTorrents$torrents) ||
+    if (!(other is Query$ListTorrents$torrentDaemon$torrents) ||
         runtimeType != other.runtimeType) {
       return false;
     }
@@ -1469,50 +1374,51 @@ class Query$ListTorrents$torrents {
   }
 }
 
-extension UtilityExtension$Query$ListTorrents$torrents
-    on Query$ListTorrents$torrents {
-  CopyWith$Query$ListTorrents$torrents<Query$ListTorrents$torrents>
-      get copyWith => CopyWith$Query$ListTorrents$torrents(
+extension UtilityExtension$Query$ListTorrents$torrentDaemon$torrents
+    on Query$ListTorrents$torrentDaemon$torrents {
+  CopyWith$Query$ListTorrents$torrentDaemon$torrents<
+          Query$ListTorrents$torrentDaemon$torrents>
+      get copyWith => CopyWith$Query$ListTorrents$torrentDaemon$torrents(
             this,
             (i) => i,
           );
 }
 
-abstract class CopyWith$Query$ListTorrents$torrents<TRes> {
-  factory CopyWith$Query$ListTorrents$torrents(
-    Query$ListTorrents$torrents instance,
-    TRes Function(Query$ListTorrents$torrents) then,
-  ) = _CopyWithImpl$Query$ListTorrents$torrents;
+abstract class CopyWith$Query$ListTorrents$torrentDaemon$torrents<TRes> {
+  factory CopyWith$Query$ListTorrents$torrentDaemon$torrents(
+    Query$ListTorrents$torrentDaemon$torrents instance,
+    TRes Function(Query$ListTorrents$torrentDaemon$torrents) then,
+  ) = _CopyWithImpl$Query$ListTorrents$torrentDaemon$torrents;
 
-  factory CopyWith$Query$ListTorrents$torrents.stub(TRes res) =
-      _CopyWithStubImpl$Query$ListTorrents$torrents;
+  factory CopyWith$Query$ListTorrents$torrentDaemon$torrents.stub(TRes res) =
+      _CopyWithStubImpl$Query$ListTorrents$torrentDaemon$torrents;
 
   TRes call({
     String? name,
     String? infohash,
     int? bytesCompleted,
     int? bytesMissing,
-    List<Query$ListTorrents$torrents$peers>? peers,
+    List<Query$ListTorrents$torrentDaemon$torrents$peers>? peers,
     String? $__typename,
   });
   TRes peers(
-      Iterable<Query$ListTorrents$torrents$peers> Function(
+      Iterable<Query$ListTorrents$torrentDaemon$torrents$peers> Function(
               Iterable<
-                  CopyWith$Query$ListTorrents$torrents$peers<
-                      Query$ListTorrents$torrents$peers>>)
+                  CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers<
+                      Query$ListTorrents$torrentDaemon$torrents$peers>>)
           _fn);
 }
 
-class _CopyWithImpl$Query$ListTorrents$torrents<TRes>
-    implements CopyWith$Query$ListTorrents$torrents<TRes> {
-  _CopyWithImpl$Query$ListTorrents$torrents(
+class _CopyWithImpl$Query$ListTorrents$torrentDaemon$torrents<TRes>
+    implements CopyWith$Query$ListTorrents$torrentDaemon$torrents<TRes> {
+  _CopyWithImpl$Query$ListTorrents$torrentDaemon$torrents(
     this._instance,
     this._then,
   );
 
-  final Query$ListTorrents$torrents _instance;
+  final Query$ListTorrents$torrentDaemon$torrents _instance;
 
-  final TRes Function(Query$ListTorrents$torrents) _then;
+  final TRes Function(Query$ListTorrents$torrentDaemon$torrents) _then;
 
   static const _undefined = <dynamic, dynamic>{};
 
@@ -1524,7 +1430,7 @@ class _CopyWithImpl$Query$ListTorrents$torrents<TRes>
     Object? peers = _undefined,
     Object? $__typename = _undefined,
   }) =>
-      _then(Query$ListTorrents$torrents(
+      _then(Query$ListTorrents$torrentDaemon$torrents(
         name: name == _undefined || name == null
             ? _instance.name
             : (name as String),
@@ -1539,29 +1445,29 @@ class _CopyWithImpl$Query$ListTorrents$torrents<TRes>
             : (bytesMissing as int),
         peers: peers == _undefined || peers == null
             ? _instance.peers
-            : (peers as List<Query$ListTorrents$torrents$peers>),
+            : (peers as List<Query$ListTorrents$torrentDaemon$torrents$peers>),
         $__typename: $__typename == _undefined || $__typename == null
             ? _instance.$__typename
             : ($__typename as String),
       ));
 
   TRes peers(
-          Iterable<Query$ListTorrents$torrents$peers> Function(
+          Iterable<Query$ListTorrents$torrentDaemon$torrents$peers> Function(
                   Iterable<
-                      CopyWith$Query$ListTorrents$torrents$peers<
-                          Query$ListTorrents$torrents$peers>>)
+                      CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers<
+                          Query$ListTorrents$torrentDaemon$torrents$peers>>)
               _fn) =>
       call(
-          peers: _fn(_instance.peers
-              .map((e) => CopyWith$Query$ListTorrents$torrents$peers(
+          peers: _fn(_instance.peers.map(
+              (e) => CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers(
                     e,
                     (i) => i,
                   ))).toList());
 }
 
-class _CopyWithStubImpl$Query$ListTorrents$torrents<TRes>
-    implements CopyWith$Query$ListTorrents$torrents<TRes> {
-  _CopyWithStubImpl$Query$ListTorrents$torrents(this._res);
+class _CopyWithStubImpl$Query$ListTorrents$torrentDaemon$torrents<TRes>
+    implements CopyWith$Query$ListTorrents$torrentDaemon$torrents<TRes> {
+  _CopyWithStubImpl$Query$ListTorrents$torrentDaemon$torrents(this._res);
 
   TRes _res;
 
@@ -1570,7 +1476,7 @@ class _CopyWithStubImpl$Query$ListTorrents$torrents<TRes>
     String? infohash,
     int? bytesCompleted,
     int? bytesMissing,
-    List<Query$ListTorrents$torrents$peers>? peers,
+    List<Query$ListTorrents$torrentDaemon$torrents$peers>? peers,
     String? $__typename,
   }) =>
       _res;
@@ -1578,21 +1484,21 @@ class _CopyWithStubImpl$Query$ListTorrents$torrents<TRes>
   peers(_fn) => _res;
 }
 
-class Query$ListTorrents$torrents$peers {
-  Query$ListTorrents$torrents$peers({
+class Query$ListTorrents$torrentDaemon$torrents$peers {
+  Query$ListTorrents$torrentDaemon$torrents$peers({
     required this.ip,
     required this.downloadRate,
     required this.clientName,
     this.$__typename = 'TorrentPeer',
   });
 
-  factory Query$ListTorrents$torrents$peers.fromJson(
+  factory Query$ListTorrents$torrentDaemon$torrents$peers.fromJson(
       Map<String, dynamic> json) {
     final l$ip = json['ip'];
     final l$downloadRate = json['downloadRate'];
     final l$clientName = json['clientName'];
     final l$$__typename = json['__typename'];
-    return Query$ListTorrents$torrents$peers(
+    return Query$ListTorrents$torrentDaemon$torrents$peers(
       ip: (l$ip as String),
       downloadRate: (l$downloadRate as num).toDouble(),
       clientName: (l$clientName as String),
@@ -1640,7 +1546,7 @@ class Query$ListTorrents$torrents$peers {
     if (identical(this, other)) {
       return true;
     }
-    if (!(other is Query$ListTorrents$torrents$peers) ||
+    if (!(other is Query$ListTorrents$torrentDaemon$torrents$peers) ||
         runtimeType != other.runtimeType) {
       return false;
     }
@@ -1668,23 +1574,25 @@ class Query$ListTorrents$torrents$peers {
   }
 }
 
-extension UtilityExtension$Query$ListTorrents$torrents$peers
-    on Query$ListTorrents$torrents$peers {
-  CopyWith$Query$ListTorrents$torrents$peers<Query$ListTorrents$torrents$peers>
-      get copyWith => CopyWith$Query$ListTorrents$torrents$peers(
+extension UtilityExtension$Query$ListTorrents$torrentDaemon$torrents$peers
+    on Query$ListTorrents$torrentDaemon$torrents$peers {
+  CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers<
+          Query$ListTorrents$torrentDaemon$torrents$peers>
+      get copyWith => CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers(
             this,
             (i) => i,
           );
 }
 
-abstract class CopyWith$Query$ListTorrents$torrents$peers<TRes> {
-  factory CopyWith$Query$ListTorrents$torrents$peers(
-    Query$ListTorrents$torrents$peers instance,
-    TRes Function(Query$ListTorrents$torrents$peers) then,
-  ) = _CopyWithImpl$Query$ListTorrents$torrents$peers;
+abstract class CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers<TRes> {
+  factory CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers(
+    Query$ListTorrents$torrentDaemon$torrents$peers instance,
+    TRes Function(Query$ListTorrents$torrentDaemon$torrents$peers) then,
+  ) = _CopyWithImpl$Query$ListTorrents$torrentDaemon$torrents$peers;
 
-  factory CopyWith$Query$ListTorrents$torrents$peers.stub(TRes res) =
-      _CopyWithStubImpl$Query$ListTorrents$torrents$peers;
+  factory CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers.stub(
+          TRes res) =
+      _CopyWithStubImpl$Query$ListTorrents$torrentDaemon$torrents$peers;
 
   TRes call({
     String? ip,
@@ -1694,16 +1602,16 @@ abstract class CopyWith$Query$ListTorrents$torrents$peers<TRes> {
   });
 }
 
-class _CopyWithImpl$Query$ListTorrents$torrents$peers<TRes>
-    implements CopyWith$Query$ListTorrents$torrents$peers<TRes> {
-  _CopyWithImpl$Query$ListTorrents$torrents$peers(
+class _CopyWithImpl$Query$ListTorrents$torrentDaemon$torrents$peers<TRes>
+    implements CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers<TRes> {
+  _CopyWithImpl$Query$ListTorrents$torrentDaemon$torrents$peers(
     this._instance,
     this._then,
   );
 
-  final Query$ListTorrents$torrents$peers _instance;
+  final Query$ListTorrents$torrentDaemon$torrents$peers _instance;
 
-  final TRes Function(Query$ListTorrents$torrents$peers) _then;
+  final TRes Function(Query$ListTorrents$torrentDaemon$torrents$peers) _then;
 
   static const _undefined = <dynamic, dynamic>{};
 
@@ -1713,7 +1621,7 @@ class _CopyWithImpl$Query$ListTorrents$torrents$peers<TRes>
     Object? clientName = _undefined,
     Object? $__typename = _undefined,
   }) =>
-      _then(Query$ListTorrents$torrents$peers(
+      _then(Query$ListTorrents$torrentDaemon$torrents$peers(
         ip: ip == _undefined || ip == null ? _instance.ip : (ip as String),
         downloadRate: downloadRate == _undefined || downloadRate == null
             ? _instance.downloadRate
@@ -1727,9 +1635,9 @@ class _CopyWithImpl$Query$ListTorrents$torrents$peers<TRes>
       ));
 }
 
-class _CopyWithStubImpl$Query$ListTorrents$torrents$peers<TRes>
-    implements CopyWith$Query$ListTorrents$torrents$peers<TRes> {
-  _CopyWithStubImpl$Query$ListTorrents$torrents$peers(this._res);
+class _CopyWithStubImpl$Query$ListTorrents$torrentDaemon$torrents$peers<TRes>
+    implements CopyWith$Query$ListTorrents$torrentDaemon$torrents$peers<TRes> {
+  _CopyWithStubImpl$Query$ListTorrents$torrentDaemon$torrents$peers(this._res);
 
   TRes _res;
 
diff --git a/ui/lib/api/torrent_stats.graphql b/ui/lib/api/torrent_stats.graphql
new file mode 100644
index 0000000..1a5f6b9
--- /dev/null
+++ b/ui/lib/api/torrent_stats.graphql
@@ -0,0 +1,13 @@
+query TorrentTotalStats($since: DateTime!) {
+  torrentDaemon {
+    statsHistory(since: $since) {
+       timestamp
+       downloadedBytes
+       uploadedBytes
+       totalPeers
+       activePeers
+       connectedSeeders
+      }
+    
+  }
+}
\ No newline at end of file
diff --git a/ui/lib/api/torrent_stats.graphql.dart b/ui/lib/api/torrent_stats.graphql.dart
new file mode 100644
index 0000000..12e7ab6
--- /dev/null
+++ b/ui/lib/api/torrent_stats.graphql.dart
@@ -0,0 +1,901 @@
+// ignore_for_file: type=lint
+import 'dart:async';
+import 'package:flutter/widgets.dart' as widgets;
+import 'package:gql/ast.dart';
+import 'package:graphql/client.dart' as graphql;
+import 'package:graphql_flutter/graphql_flutter.dart' as graphql_flutter;
+
+class Variables$Query$TorrentTotalStats {
+  factory Variables$Query$TorrentTotalStats({required DateTime since}) =>
+      Variables$Query$TorrentTotalStats._({
+        r'since': since,
+      });
+
+  Variables$Query$TorrentTotalStats._(this._$data);
+
+  factory Variables$Query$TorrentTotalStats.fromJson(
+      Map<String, dynamic> data) {
+    final result$data = <String, dynamic>{};
+    final l$since = data['since'];
+    result$data['since'] = DateTime.parse((l$since as String));
+    return Variables$Query$TorrentTotalStats._(result$data);
+  }
+
+  Map<String, dynamic> _$data;
+
+  DateTime get since => (_$data['since'] as DateTime);
+
+  Map<String, dynamic> toJson() {
+    final result$data = <String, dynamic>{};
+    final l$since = since;
+    result$data['since'] = l$since.toIso8601String();
+    return result$data;
+  }
+
+  CopyWith$Variables$Query$TorrentTotalStats<Variables$Query$TorrentTotalStats>
+      get copyWith => CopyWith$Variables$Query$TorrentTotalStats(
+            this,
+            (i) => i,
+          );
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (!(other is Variables$Query$TorrentTotalStats) ||
+        runtimeType != other.runtimeType) {
+      return false;
+    }
+    final l$since = since;
+    final lOther$since = other.since;
+    if (l$since != lOther$since) {
+      return false;
+    }
+    return true;
+  }
+
+  @override
+  int get hashCode {
+    final l$since = since;
+    return Object.hashAll([l$since]);
+  }
+}
+
+abstract class CopyWith$Variables$Query$TorrentTotalStats<TRes> {
+  factory CopyWith$Variables$Query$TorrentTotalStats(
+    Variables$Query$TorrentTotalStats instance,
+    TRes Function(Variables$Query$TorrentTotalStats) then,
+  ) = _CopyWithImpl$Variables$Query$TorrentTotalStats;
+
+  factory CopyWith$Variables$Query$TorrentTotalStats.stub(TRes res) =
+      _CopyWithStubImpl$Variables$Query$TorrentTotalStats;
+
+  TRes call({DateTime? since});
+}
+
+class _CopyWithImpl$Variables$Query$TorrentTotalStats<TRes>
+    implements CopyWith$Variables$Query$TorrentTotalStats<TRes> {
+  _CopyWithImpl$Variables$Query$TorrentTotalStats(
+    this._instance,
+    this._then,
+  );
+
+  final Variables$Query$TorrentTotalStats _instance;
+
+  final TRes Function(Variables$Query$TorrentTotalStats) _then;
+
+  static const _undefined = <dynamic, dynamic>{};
+
+  TRes call({Object? since = _undefined}) =>
+      _then(Variables$Query$TorrentTotalStats._({
+        ..._instance._$data,
+        if (since != _undefined && since != null) 'since': (since as DateTime),
+      }));
+}
+
+class _CopyWithStubImpl$Variables$Query$TorrentTotalStats<TRes>
+    implements CopyWith$Variables$Query$TorrentTotalStats<TRes> {
+  _CopyWithStubImpl$Variables$Query$TorrentTotalStats(this._res);
+
+  TRes _res;
+
+  call({DateTime? since}) => _res;
+}
+
+class Query$TorrentTotalStats {
+  Query$TorrentTotalStats({
+    this.torrentDaemon,
+    this.$__typename = 'Query',
+  });
+
+  factory Query$TorrentTotalStats.fromJson(Map<String, dynamic> json) {
+    final l$torrentDaemon = json['torrentDaemon'];
+    final l$$__typename = json['__typename'];
+    return Query$TorrentTotalStats(
+      torrentDaemon: l$torrentDaemon == null
+          ? null
+          : Query$TorrentTotalStats$torrentDaemon.fromJson(
+              (l$torrentDaemon as Map<String, dynamic>)),
+      $__typename: (l$$__typename as String),
+    );
+  }
+
+  final Query$TorrentTotalStats$torrentDaemon? torrentDaemon;
+
+  final String $__typename;
+
+  Map<String, dynamic> toJson() {
+    final _resultData = <String, dynamic>{};
+    final l$torrentDaemon = torrentDaemon;
+    _resultData['torrentDaemon'] = l$torrentDaemon?.toJson();
+    final l$$__typename = $__typename;
+    _resultData['__typename'] = l$$__typename;
+    return _resultData;
+  }
+
+  @override
+  int get hashCode {
+    final l$torrentDaemon = torrentDaemon;
+    final l$$__typename = $__typename;
+    return Object.hashAll([
+      l$torrentDaemon,
+      l$$__typename,
+    ]);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (!(other is Query$TorrentTotalStats) ||
+        runtimeType != other.runtimeType) {
+      return false;
+    }
+    final l$torrentDaemon = torrentDaemon;
+    final lOther$torrentDaemon = other.torrentDaemon;
+    if (l$torrentDaemon != lOther$torrentDaemon) {
+      return false;
+    }
+    final l$$__typename = $__typename;
+    final lOther$$__typename = other.$__typename;
+    if (l$$__typename != lOther$$__typename) {
+      return false;
+    }
+    return true;
+  }
+}
+
+extension UtilityExtension$Query$TorrentTotalStats on Query$TorrentTotalStats {
+  CopyWith$Query$TorrentTotalStats<Query$TorrentTotalStats> get copyWith =>
+      CopyWith$Query$TorrentTotalStats(
+        this,
+        (i) => i,
+      );
+}
+
+abstract class CopyWith$Query$TorrentTotalStats<TRes> {
+  factory CopyWith$Query$TorrentTotalStats(
+    Query$TorrentTotalStats instance,
+    TRes Function(Query$TorrentTotalStats) then,
+  ) = _CopyWithImpl$Query$TorrentTotalStats;
+
+  factory CopyWith$Query$TorrentTotalStats.stub(TRes res) =
+      _CopyWithStubImpl$Query$TorrentTotalStats;
+
+  TRes call({
+    Query$TorrentTotalStats$torrentDaemon? torrentDaemon,
+    String? $__typename,
+  });
+  CopyWith$Query$TorrentTotalStats$torrentDaemon<TRes> get torrentDaemon;
+}
+
+class _CopyWithImpl$Query$TorrentTotalStats<TRes>
+    implements CopyWith$Query$TorrentTotalStats<TRes> {
+  _CopyWithImpl$Query$TorrentTotalStats(
+    this._instance,
+    this._then,
+  );
+
+  final Query$TorrentTotalStats _instance;
+
+  final TRes Function(Query$TorrentTotalStats) _then;
+
+  static const _undefined = <dynamic, dynamic>{};
+
+  TRes call({
+    Object? torrentDaemon = _undefined,
+    Object? $__typename = _undefined,
+  }) =>
+      _then(Query$TorrentTotalStats(
+        torrentDaemon: torrentDaemon == _undefined
+            ? _instance.torrentDaemon
+            : (torrentDaemon as Query$TorrentTotalStats$torrentDaemon?),
+        $__typename: $__typename == _undefined || $__typename == null
+            ? _instance.$__typename
+            : ($__typename as String),
+      ));
+
+  CopyWith$Query$TorrentTotalStats$torrentDaemon<TRes> get torrentDaemon {
+    final local$torrentDaemon = _instance.torrentDaemon;
+    return local$torrentDaemon == null
+        ? CopyWith$Query$TorrentTotalStats$torrentDaemon.stub(_then(_instance))
+        : CopyWith$Query$TorrentTotalStats$torrentDaemon(
+            local$torrentDaemon, (e) => call(torrentDaemon: e));
+  }
+}
+
+class _CopyWithStubImpl$Query$TorrentTotalStats<TRes>
+    implements CopyWith$Query$TorrentTotalStats<TRes> {
+  _CopyWithStubImpl$Query$TorrentTotalStats(this._res);
+
+  TRes _res;
+
+  call({
+    Query$TorrentTotalStats$torrentDaemon? torrentDaemon,
+    String? $__typename,
+  }) =>
+      _res;
+
+  CopyWith$Query$TorrentTotalStats$torrentDaemon<TRes> get torrentDaemon =>
+      CopyWith$Query$TorrentTotalStats$torrentDaemon.stub(_res);
+}
+
+const documentNodeQueryTorrentTotalStats = DocumentNode(definitions: [
+  OperationDefinitionNode(
+    type: OperationType.query,
+    name: NameNode(value: 'TorrentTotalStats'),
+    variableDefinitions: [
+      VariableDefinitionNode(
+        variable: VariableNode(name: NameNode(value: 'since')),
+        type: NamedTypeNode(
+          name: NameNode(value: 'DateTime'),
+          isNonNull: true,
+        ),
+        defaultValue: DefaultValueNode(value: null),
+        directives: [],
+      )
+    ],
+    directives: [],
+    selectionSet: SelectionSetNode(selections: [
+      FieldNode(
+        name: NameNode(value: 'torrentDaemon'),
+        alias: null,
+        arguments: [],
+        directives: [],
+        selectionSet: SelectionSetNode(selections: [
+          FieldNode(
+            name: NameNode(value: 'statsHistory'),
+            alias: null,
+            arguments: [
+              ArgumentNode(
+                name: NameNode(value: 'since'),
+                value: VariableNode(name: NameNode(value: 'since')),
+              )
+            ],
+            directives: [],
+            selectionSet: SelectionSetNode(selections: [
+              FieldNode(
+                name: NameNode(value: 'timestamp'),
+                alias: null,
+                arguments: [],
+                directives: [],
+                selectionSet: null,
+              ),
+              FieldNode(
+                name: NameNode(value: 'downloadedBytes'),
+                alias: null,
+                arguments: [],
+                directives: [],
+                selectionSet: null,
+              ),
+              FieldNode(
+                name: NameNode(value: 'uploadedBytes'),
+                alias: null,
+                arguments: [],
+                directives: [],
+                selectionSet: null,
+              ),
+              FieldNode(
+                name: NameNode(value: 'totalPeers'),
+                alias: null,
+                arguments: [],
+                directives: [],
+                selectionSet: null,
+              ),
+              FieldNode(
+                name: NameNode(value: 'activePeers'),
+                alias: null,
+                arguments: [],
+                directives: [],
+                selectionSet: null,
+              ),
+              FieldNode(
+                name: NameNode(value: 'connectedSeeders'),
+                alias: null,
+                arguments: [],
+                directives: [],
+                selectionSet: null,
+              ),
+              FieldNode(
+                name: NameNode(value: '__typename'),
+                alias: null,
+                arguments: [],
+                directives: [],
+                selectionSet: null,
+              ),
+            ]),
+          ),
+          FieldNode(
+            name: NameNode(value: '__typename'),
+            alias: null,
+            arguments: [],
+            directives: [],
+            selectionSet: null,
+          ),
+        ]),
+      ),
+      FieldNode(
+        name: NameNode(value: '__typename'),
+        alias: null,
+        arguments: [],
+        directives: [],
+        selectionSet: null,
+      ),
+    ]),
+  ),
+]);
+Query$TorrentTotalStats _parserFn$Query$TorrentTotalStats(
+        Map<String, dynamic> data) =>
+    Query$TorrentTotalStats.fromJson(data);
+typedef OnQueryComplete$Query$TorrentTotalStats = FutureOr<void> Function(
+  Map<String, dynamic>?,
+  Query$TorrentTotalStats?,
+);
+
+class Options$Query$TorrentTotalStats
+    extends graphql.QueryOptions<Query$TorrentTotalStats> {
+  Options$Query$TorrentTotalStats({
+    String? operationName,
+    required Variables$Query$TorrentTotalStats variables,
+    graphql.FetchPolicy? fetchPolicy,
+    graphql.ErrorPolicy? errorPolicy,
+    graphql.CacheRereadPolicy? cacheRereadPolicy,
+    Object? optimisticResult,
+    Query$TorrentTotalStats? typedOptimisticResult,
+    Duration? pollInterval,
+    graphql.Context? context,
+    OnQueryComplete$Query$TorrentTotalStats? onComplete,
+    graphql.OnQueryError? onError,
+  })  : onCompleteWithParsed = onComplete,
+        super(
+          variables: variables.toJson(),
+          operationName: operationName,
+          fetchPolicy: fetchPolicy,
+          errorPolicy: errorPolicy,
+          cacheRereadPolicy: cacheRereadPolicy,
+          optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(),
+          pollInterval: pollInterval,
+          context: context,
+          onComplete: onComplete == null
+              ? null
+              : (data) => onComplete(
+                    data,
+                    data == null
+                        ? null
+                        : _parserFn$Query$TorrentTotalStats(data),
+                  ),
+          onError: onError,
+          document: documentNodeQueryTorrentTotalStats,
+          parserFn: _parserFn$Query$TorrentTotalStats,
+        );
+
+  final OnQueryComplete$Query$TorrentTotalStats? onCompleteWithParsed;
+
+  @override
+  List<Object?> get properties => [
+        ...super.onComplete == null
+            ? super.properties
+            : super.properties.where((property) => property != onComplete),
+        onCompleteWithParsed,
+      ];
+}
+
+class WatchOptions$Query$TorrentTotalStats
+    extends graphql.WatchQueryOptions<Query$TorrentTotalStats> {
+  WatchOptions$Query$TorrentTotalStats({
+    String? operationName,
+    required Variables$Query$TorrentTotalStats variables,
+    graphql.FetchPolicy? fetchPolicy,
+    graphql.ErrorPolicy? errorPolicy,
+    graphql.CacheRereadPolicy? cacheRereadPolicy,
+    Object? optimisticResult,
+    Query$TorrentTotalStats? typedOptimisticResult,
+    graphql.Context? context,
+    Duration? pollInterval,
+    bool? eagerlyFetchResults,
+    bool carryForwardDataOnException = true,
+    bool fetchResults = false,
+  }) : super(
+          variables: variables.toJson(),
+          operationName: operationName,
+          fetchPolicy: fetchPolicy,
+          errorPolicy: errorPolicy,
+          cacheRereadPolicy: cacheRereadPolicy,
+          optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(),
+          context: context,
+          document: documentNodeQueryTorrentTotalStats,
+          pollInterval: pollInterval,
+          eagerlyFetchResults: eagerlyFetchResults,
+          carryForwardDataOnException: carryForwardDataOnException,
+          fetchResults: fetchResults,
+          parserFn: _parserFn$Query$TorrentTotalStats,
+        );
+}
+
+class FetchMoreOptions$Query$TorrentTotalStats
+    extends graphql.FetchMoreOptions {
+  FetchMoreOptions$Query$TorrentTotalStats({
+    required graphql.UpdateQuery updateQuery,
+    required Variables$Query$TorrentTotalStats variables,
+  }) : super(
+          updateQuery: updateQuery,
+          variables: variables.toJson(),
+          document: documentNodeQueryTorrentTotalStats,
+        );
+}
+
+extension ClientExtension$Query$TorrentTotalStats on graphql.GraphQLClient {
+  Future<graphql.QueryResult<Query$TorrentTotalStats>> query$TorrentTotalStats(
+          Options$Query$TorrentTotalStats options) async =>
+      await this.query(options);
+  graphql.ObservableQuery<Query$TorrentTotalStats> watchQuery$TorrentTotalStats(
+          WatchOptions$Query$TorrentTotalStats options) =>
+      this.watchQuery(options);
+  void writeQuery$TorrentTotalStats({
+    required Query$TorrentTotalStats data,
+    required Variables$Query$TorrentTotalStats variables,
+    bool broadcast = true,
+  }) =>
+      this.writeQuery(
+        graphql.Request(
+          operation:
+              graphql.Operation(document: documentNodeQueryTorrentTotalStats),
+          variables: variables.toJson(),
+        ),
+        data: data.toJson(),
+        broadcast: broadcast,
+      );
+  Query$TorrentTotalStats? readQuery$TorrentTotalStats({
+    required Variables$Query$TorrentTotalStats variables,
+    bool optimistic = true,
+  }) {
+    final result = this.readQuery(
+      graphql.Request(
+        operation:
+            graphql.Operation(document: documentNodeQueryTorrentTotalStats),
+        variables: variables.toJson(),
+      ),
+      optimistic: optimistic,
+    );
+    return result == null ? null : Query$TorrentTotalStats.fromJson(result);
+  }
+}
+
+graphql_flutter.QueryHookResult<Query$TorrentTotalStats>
+    useQuery$TorrentTotalStats(Options$Query$TorrentTotalStats options) =>
+        graphql_flutter.useQuery(options);
+graphql.ObservableQuery<Query$TorrentTotalStats>
+    useWatchQuery$TorrentTotalStats(
+            WatchOptions$Query$TorrentTotalStats options) =>
+        graphql_flutter.useWatchQuery(options);
+
+class Query$TorrentTotalStats$Widget
+    extends graphql_flutter.Query<Query$TorrentTotalStats> {
+  Query$TorrentTotalStats$Widget({
+    widgets.Key? key,
+    required Options$Query$TorrentTotalStats options,
+    required graphql_flutter.QueryBuilder<Query$TorrentTotalStats> builder,
+  }) : super(
+          key: key,
+          options: options,
+          builder: builder,
+        );
+}
+
+class Query$TorrentTotalStats$torrentDaemon {
+  Query$TorrentTotalStats$torrentDaemon({
+    required this.statsHistory,
+    this.$__typename = 'TorrentDaemonQuery',
+  });
+
+  factory Query$TorrentTotalStats$torrentDaemon.fromJson(
+      Map<String, dynamic> json) {
+    final l$statsHistory = json['statsHistory'];
+    final l$$__typename = json['__typename'];
+    return Query$TorrentTotalStats$torrentDaemon(
+      statsHistory: (l$statsHistory as List<dynamic>)
+          .map((e) =>
+              Query$TorrentTotalStats$torrentDaemon$statsHistory.fromJson(
+                  (e as Map<String, dynamic>)))
+          .toList(),
+      $__typename: (l$$__typename as String),
+    );
+  }
+
+  final List<Query$TorrentTotalStats$torrentDaemon$statsHistory> statsHistory;
+
+  final String $__typename;
+
+  Map<String, dynamic> toJson() {
+    final _resultData = <String, dynamic>{};
+    final l$statsHistory = statsHistory;
+    _resultData['statsHistory'] =
+        l$statsHistory.map((e) => e.toJson()).toList();
+    final l$$__typename = $__typename;
+    _resultData['__typename'] = l$$__typename;
+    return _resultData;
+  }
+
+  @override
+  int get hashCode {
+    final l$statsHistory = statsHistory;
+    final l$$__typename = $__typename;
+    return Object.hashAll([
+      Object.hashAll(l$statsHistory.map((v) => v)),
+      l$$__typename,
+    ]);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (!(other is Query$TorrentTotalStats$torrentDaemon) ||
+        runtimeType != other.runtimeType) {
+      return false;
+    }
+    final l$statsHistory = statsHistory;
+    final lOther$statsHistory = other.statsHistory;
+    if (l$statsHistory.length != lOther$statsHistory.length) {
+      return false;
+    }
+    for (int i = 0; i < l$statsHistory.length; i++) {
+      final l$statsHistory$entry = l$statsHistory[i];
+      final lOther$statsHistory$entry = lOther$statsHistory[i];
+      if (l$statsHistory$entry != lOther$statsHistory$entry) {
+        return false;
+      }
+    }
+    final l$$__typename = $__typename;
+    final lOther$$__typename = other.$__typename;
+    if (l$$__typename != lOther$$__typename) {
+      return false;
+    }
+    return true;
+  }
+}
+
+extension UtilityExtension$Query$TorrentTotalStats$torrentDaemon
+    on Query$TorrentTotalStats$torrentDaemon {
+  CopyWith$Query$TorrentTotalStats$torrentDaemon<
+          Query$TorrentTotalStats$torrentDaemon>
+      get copyWith => CopyWith$Query$TorrentTotalStats$torrentDaemon(
+            this,
+            (i) => i,
+          );
+}
+
+abstract class CopyWith$Query$TorrentTotalStats$torrentDaemon<TRes> {
+  factory CopyWith$Query$TorrentTotalStats$torrentDaemon(
+    Query$TorrentTotalStats$torrentDaemon instance,
+    TRes Function(Query$TorrentTotalStats$torrentDaemon) then,
+  ) = _CopyWithImpl$Query$TorrentTotalStats$torrentDaemon;
+
+  factory CopyWith$Query$TorrentTotalStats$torrentDaemon.stub(TRes res) =
+      _CopyWithStubImpl$Query$TorrentTotalStats$torrentDaemon;
+
+  TRes call({
+    List<Query$TorrentTotalStats$torrentDaemon$statsHistory>? statsHistory,
+    String? $__typename,
+  });
+  TRes statsHistory(
+      Iterable<Query$TorrentTotalStats$torrentDaemon$statsHistory> Function(
+              Iterable<
+                  CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory<
+                      Query$TorrentTotalStats$torrentDaemon$statsHistory>>)
+          _fn);
+}
+
+class _CopyWithImpl$Query$TorrentTotalStats$torrentDaemon<TRes>
+    implements CopyWith$Query$TorrentTotalStats$torrentDaemon<TRes> {
+  _CopyWithImpl$Query$TorrentTotalStats$torrentDaemon(
+    this._instance,
+    this._then,
+  );
+
+  final Query$TorrentTotalStats$torrentDaemon _instance;
+
+  final TRes Function(Query$TorrentTotalStats$torrentDaemon) _then;
+
+  static const _undefined = <dynamic, dynamic>{};
+
+  TRes call({
+    Object? statsHistory = _undefined,
+    Object? $__typename = _undefined,
+  }) =>
+      _then(Query$TorrentTotalStats$torrentDaemon(
+        statsHistory: statsHistory == _undefined || statsHistory == null
+            ? _instance.statsHistory
+            : (statsHistory
+                as List<Query$TorrentTotalStats$torrentDaemon$statsHistory>),
+        $__typename: $__typename == _undefined || $__typename == null
+            ? _instance.$__typename
+            : ($__typename as String),
+      ));
+
+  TRes statsHistory(
+          Iterable<Query$TorrentTotalStats$torrentDaemon$statsHistory> Function(
+                  Iterable<
+                      CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory<
+                          Query$TorrentTotalStats$torrentDaemon$statsHistory>>)
+              _fn) =>
+      call(
+          statsHistory: _fn(_instance.statsHistory.map((e) =>
+              CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory(
+                e,
+                (i) => i,
+              ))).toList());
+}
+
+class _CopyWithStubImpl$Query$TorrentTotalStats$torrentDaemon<TRes>
+    implements CopyWith$Query$TorrentTotalStats$torrentDaemon<TRes> {
+  _CopyWithStubImpl$Query$TorrentTotalStats$torrentDaemon(this._res);
+
+  TRes _res;
+
+  call({
+    List<Query$TorrentTotalStats$torrentDaemon$statsHistory>? statsHistory,
+    String? $__typename,
+  }) =>
+      _res;
+
+  statsHistory(_fn) => _res;
+}
+
+class Query$TorrentTotalStats$torrentDaemon$statsHistory {
+  Query$TorrentTotalStats$torrentDaemon$statsHistory({
+    required this.timestamp,
+    required this.downloadedBytes,
+    required this.uploadedBytes,
+    required this.totalPeers,
+    required this.activePeers,
+    required this.connectedSeeders,
+    this.$__typename = 'TorrentStats',
+  });
+
+  factory Query$TorrentTotalStats$torrentDaemon$statsHistory.fromJson(
+      Map<String, dynamic> json) {
+    final l$timestamp = json['timestamp'];
+    final l$downloadedBytes = json['downloadedBytes'];
+    final l$uploadedBytes = json['uploadedBytes'];
+    final l$totalPeers = json['totalPeers'];
+    final l$activePeers = json['activePeers'];
+    final l$connectedSeeders = json['connectedSeeders'];
+    final l$$__typename = json['__typename'];
+    return Query$TorrentTotalStats$torrentDaemon$statsHistory(
+      timestamp: DateTime.parse((l$timestamp as String)),
+      downloadedBytes: (l$downloadedBytes as int),
+      uploadedBytes: (l$uploadedBytes as int),
+      totalPeers: (l$totalPeers as int),
+      activePeers: (l$activePeers as int),
+      connectedSeeders: (l$connectedSeeders as int),
+      $__typename: (l$$__typename as String),
+    );
+  }
+
+  final DateTime timestamp;
+
+  final int downloadedBytes;
+
+  final int uploadedBytes;
+
+  final int totalPeers;
+
+  final int activePeers;
+
+  final int connectedSeeders;
+
+  final String $__typename;
+
+  Map<String, dynamic> toJson() {
+    final _resultData = <String, dynamic>{};
+    final l$timestamp = timestamp;
+    _resultData['timestamp'] = l$timestamp.toIso8601String();
+    final l$downloadedBytes = downloadedBytes;
+    _resultData['downloadedBytes'] = l$downloadedBytes;
+    final l$uploadedBytes = uploadedBytes;
+    _resultData['uploadedBytes'] = l$uploadedBytes;
+    final l$totalPeers = totalPeers;
+    _resultData['totalPeers'] = l$totalPeers;
+    final l$activePeers = activePeers;
+    _resultData['activePeers'] = l$activePeers;
+    final l$connectedSeeders = connectedSeeders;
+    _resultData['connectedSeeders'] = l$connectedSeeders;
+    final l$$__typename = $__typename;
+    _resultData['__typename'] = l$$__typename;
+    return _resultData;
+  }
+
+  @override
+  int get hashCode {
+    final l$timestamp = timestamp;
+    final l$downloadedBytes = downloadedBytes;
+    final l$uploadedBytes = uploadedBytes;
+    final l$totalPeers = totalPeers;
+    final l$activePeers = activePeers;
+    final l$connectedSeeders = connectedSeeders;
+    final l$$__typename = $__typename;
+    return Object.hashAll([
+      l$timestamp,
+      l$downloadedBytes,
+      l$uploadedBytes,
+      l$totalPeers,
+      l$activePeers,
+      l$connectedSeeders,
+      l$$__typename,
+    ]);
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (!(other is Query$TorrentTotalStats$torrentDaemon$statsHistory) ||
+        runtimeType != other.runtimeType) {
+      return false;
+    }
+    final l$timestamp = timestamp;
+    final lOther$timestamp = other.timestamp;
+    if (l$timestamp != lOther$timestamp) {
+      return false;
+    }
+    final l$downloadedBytes = downloadedBytes;
+    final lOther$downloadedBytes = other.downloadedBytes;
+    if (l$downloadedBytes != lOther$downloadedBytes) {
+      return false;
+    }
+    final l$uploadedBytes = uploadedBytes;
+    final lOther$uploadedBytes = other.uploadedBytes;
+    if (l$uploadedBytes != lOther$uploadedBytes) {
+      return false;
+    }
+    final l$totalPeers = totalPeers;
+    final lOther$totalPeers = other.totalPeers;
+    if (l$totalPeers != lOther$totalPeers) {
+      return false;
+    }
+    final l$activePeers = activePeers;
+    final lOther$activePeers = other.activePeers;
+    if (l$activePeers != lOther$activePeers) {
+      return false;
+    }
+    final l$connectedSeeders = connectedSeeders;
+    final lOther$connectedSeeders = other.connectedSeeders;
+    if (l$connectedSeeders != lOther$connectedSeeders) {
+      return false;
+    }
+    final l$$__typename = $__typename;
+    final lOther$$__typename = other.$__typename;
+    if (l$$__typename != lOther$$__typename) {
+      return false;
+    }
+    return true;
+  }
+}
+
+extension UtilityExtension$Query$TorrentTotalStats$torrentDaemon$statsHistory
+    on Query$TorrentTotalStats$torrentDaemon$statsHistory {
+  CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory<
+          Query$TorrentTotalStats$torrentDaemon$statsHistory>
+      get copyWith =>
+          CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory(
+            this,
+            (i) => i,
+          );
+}
+
+abstract class CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory<
+    TRes> {
+  factory CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory(
+    Query$TorrentTotalStats$torrentDaemon$statsHistory instance,
+    TRes Function(Query$TorrentTotalStats$torrentDaemon$statsHistory) then,
+  ) = _CopyWithImpl$Query$TorrentTotalStats$torrentDaemon$statsHistory;
+
+  factory CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory.stub(
+          TRes res) =
+      _CopyWithStubImpl$Query$TorrentTotalStats$torrentDaemon$statsHistory;
+
+  TRes call({
+    DateTime? timestamp,
+    int? downloadedBytes,
+    int? uploadedBytes,
+    int? totalPeers,
+    int? activePeers,
+    int? connectedSeeders,
+    String? $__typename,
+  });
+}
+
+class _CopyWithImpl$Query$TorrentTotalStats$torrentDaemon$statsHistory<TRes>
+    implements
+        CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory<TRes> {
+  _CopyWithImpl$Query$TorrentTotalStats$torrentDaemon$statsHistory(
+    this._instance,
+    this._then,
+  );
+
+  final Query$TorrentTotalStats$torrentDaemon$statsHistory _instance;
+
+  final TRes Function(Query$TorrentTotalStats$torrentDaemon$statsHistory) _then;
+
+  static const _undefined = <dynamic, dynamic>{};
+
+  TRes call({
+    Object? timestamp = _undefined,
+    Object? downloadedBytes = _undefined,
+    Object? uploadedBytes = _undefined,
+    Object? totalPeers = _undefined,
+    Object? activePeers = _undefined,
+    Object? connectedSeeders = _undefined,
+    Object? $__typename = _undefined,
+  }) =>
+      _then(Query$TorrentTotalStats$torrentDaemon$statsHistory(
+        timestamp: timestamp == _undefined || timestamp == null
+            ? _instance.timestamp
+            : (timestamp as DateTime),
+        downloadedBytes:
+            downloadedBytes == _undefined || downloadedBytes == null
+                ? _instance.downloadedBytes
+                : (downloadedBytes as int),
+        uploadedBytes: uploadedBytes == _undefined || uploadedBytes == null
+            ? _instance.uploadedBytes
+            : (uploadedBytes as int),
+        totalPeers: totalPeers == _undefined || totalPeers == null
+            ? _instance.totalPeers
+            : (totalPeers as int),
+        activePeers: activePeers == _undefined || activePeers == null
+            ? _instance.activePeers
+            : (activePeers as int),
+        connectedSeeders:
+            connectedSeeders == _undefined || connectedSeeders == null
+                ? _instance.connectedSeeders
+                : (connectedSeeders as int),
+        $__typename: $__typename == _undefined || $__typename == null
+            ? _instance.$__typename
+            : ($__typename as String),
+      ));
+}
+
+class _CopyWithStubImpl$Query$TorrentTotalStats$torrentDaemon$statsHistory<TRes>
+    implements
+        CopyWith$Query$TorrentTotalStats$torrentDaemon$statsHistory<TRes> {
+  _CopyWithStubImpl$Query$TorrentTotalStats$torrentDaemon$statsHistory(
+      this._res);
+
+  TRes _res;
+
+  call({
+    DateTime? timestamp,
+    int? downloadedBytes,
+    int? uploadedBytes,
+    int? totalPeers,
+    int? activePeers,
+    int? connectedSeeders,
+    String? $__typename,
+  }) =>
+      _res;
+}
diff --git a/ui/lib/screens/downloads.dart b/ui/lib/screens/downloads.dart
index 1e0aade..048da34 100644
--- a/ui/lib/screens/downloads.dart
+++ b/ui/lib/screens/downloads.dart
@@ -20,10 +20,10 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
       child: FutureBuilder(
         key: GlobalKey(),
         future: client.query$ListTorrents(Options$Query$ListTorrents(
-          variables: Variables$Query$ListTorrents(downloading: filterDownloading),
-        )),
+            // variables: Variables$Query$ListTorrents(downloading: filterDownloading),
+            )),
         builder: (context, snapshot) {
-          final torrents = snapshot.data?.parsedData?.torrents;
+          final torrents = snapshot.data?.parsedData?.torrentDaemon?.torrents;
 
           return NestedScrollView(
             floatHeaderSlivers: true,
@@ -68,7 +68,7 @@ class _DownloadsScreenState extends State<DownloadsScreen> {
 }
 
 class TorrentTile extends StatelessWidget {
-  final Query$ListTorrents$torrents torrent;
+  final Query$ListTorrents$torrentDaemon$torrents torrent;
 
   const TorrentTile({super.key, required this.torrent});
 
diff --git a/ui/lib/screens/file_view.dart b/ui/lib/screens/file_view.dart
index c8d65fd..dfab0da 100644
--- a/ui/lib/screens/file_view.dart
+++ b/ui/lib/screens/file_view.dart
@@ -9,6 +9,7 @@ import 'package:tstor_ui/components/sliver_header.dart';
 
 import 'package:tstor_ui/font/t_icons_icons.dart';
 import 'package:path/path.dart' as p;
+import 'package:tstor_ui/screens/torrent_stats.dart';
 import 'package:tstor_ui/utils/bytes.dart';
 
 class FileViewScreen extends StatefulWidget {
@@ -88,6 +89,13 @@ class _FileViewScreenState extends State<FileViewScreen> {
             icon: const Icon(Icons.arrow_upward),
           ),
           actions: [
+            IconButton(
+              onPressed: () => Navigator.push(
+                context,
+                MaterialPageRoute(builder: (context) => const TorrentStatsScreen()),
+              ),
+              icon: const Icon(Icons.trending_up),
+            ),
             IconButton(
               icon: const Icon(Icons.refresh),
               onPressed: refresh,
diff --git a/ui/lib/screens/torrent_stats.dart b/ui/lib/screens/torrent_stats.dart
new file mode 100644
index 0000000..3676a6c
--- /dev/null
+++ b/ui/lib/screens/torrent_stats.dart
@@ -0,0 +1,62 @@
+import 'package:fl_chart/fl_chart.dart';
+import 'package:flutter/material.dart';
+import 'package:graphql/client.dart';
+import 'package:tstor_ui/api/client.dart';
+import 'package:tstor_ui/api/torrent_stats.graphql.dart';
+
+class TorrentStatsScreen extends StatefulWidget {
+  const TorrentStatsScreen({super.key});
+
+  @override
+  State<TorrentStatsScreen> createState() => _TorrentStatsScreenState();
+}
+
+class _TorrentStatsScreenState extends State<TorrentStatsScreen> {
+  Future<LineChartData> _totalStats() async {
+    final since = DateTime.now().subtract(Duration(hours: 1));
+    final data = await client.query$TorrentTotalStats(
+      Options$Query$TorrentTotalStats(
+        variables: Variables$Query$TorrentTotalStats(
+          since: since,
+        ),
+        fetchPolicy: FetchPolicy.networkOnly,
+      ),
+    );
+
+    return LineChartData(
+      lineBarsData: [
+        LineChartBarData(
+          spots: data.parsedData!.torrentDaemon!.statsHistory
+              .map(
+                (e) => FlSpot(
+                  since.difference(e.timestamp).inSeconds.toDouble(),
+                  e.activePeers.toDouble(),
+                ),
+              )
+              .toList(),
+        ),
+      ],
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text("Torrent Stats"),
+      ),
+      body: FutureBuilder(
+        future: _totalStats(),
+        builder: (context, snapshot) {
+          if (!snapshot.hasData) {
+            return const Center(
+              child: CircularProgressIndicator(),
+            );
+          }
+
+          return LineChart(snapshot.data!);
+        },
+      ),
+    );
+  }
+}
diff --git a/ui/macos/Flutter/GeneratedPluginRegistrant.swift b/ui/macos/Flutter/GeneratedPluginRegistrant.swift
index 5d96bad..878356c 100644
--- a/ui/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/ui/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -10,7 +10,7 @@ import dynamic_color
 import path_provider_foundation
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
-  ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
+  ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
   DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
 }
diff --git a/ui/pubspec.lock b/ui/pubspec.lock
index 986f95d..6e481f9 100644
--- a/ui/pubspec.lock
+++ b/ui/pubspec.lock
@@ -5,18 +5,23 @@ packages:
     dependency: transitive
     description:
       name: _fe_analyzer_shared
-      sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
+      sha256: "5aaf60d96c4cd00fe7f21594b5ad6a1b699c80a27420f8a837f4d68473ef09e3"
       url: "https://pub.dev"
     source: hosted
-    version: "67.0.0"
+    version: "68.0.0"
+  _macros:
+    dependency: transitive
+    description: dart
+    source: sdk
+    version: "0.1.5"
   analyzer:
     dependency: transitive
     description:
       name: analyzer
-      sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
+      sha256: "21f1d3720fd1c70316399d5e2bccaebb415c434592d778cce8acb967b8578808"
       url: "https://pub.dev"
     source: hosted
-    version: "6.4.1"
+    version: "6.5.0"
   args:
     dependency: transitive
     description:
@@ -61,10 +66,10 @@ packages:
     dependency: transitive
     description:
       name: build_daemon
-      sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
+      sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
       url: "https://pub.dev"
     source: hosted
-    version: "4.0.1"
+    version: "4.0.2"
   build_resolvers:
     dependency: transitive
     description:
@@ -77,18 +82,18 @@ packages:
     dependency: "direct dev"
     description:
       name: build_runner
-      sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
+      sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
       url: "https://pub.dev"
     source: hosted
-    version: "2.4.9"
+    version: "2.4.11"
   build_runner_core:
     dependency: transitive
     description:
       name: build_runner_core
-      sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
+      sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe
       url: "https://pub.dev"
     source: hosted
-    version: "7.3.0"
+    version: "7.3.1"
   built_collection:
     dependency: transitive
     description:
@@ -149,18 +154,18 @@ packages:
     dependency: transitive
     description:
       name: connectivity_plus
-      sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0"
+      sha256: db7a4e143dc72cc3cb2044ef9b052a7ebfe729513e6a82943bc3526f784365b8
       url: "https://pub.dev"
     source: hosted
-    version: "5.0.2"
+    version: "6.0.3"
   connectivity_plus_platform_interface:
     dependency: transitive
     description:
       name: connectivity_plus_platform_interface
-      sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
+      sha256: b6a56efe1e6675be240de39107281d4034b64ac23438026355b4234042a35adb
       url: "https://pub.dev"
     source: hosted
-    version: "1.2.4"
+    version: "2.0.0"
   convert:
     dependency: transitive
     description:
@@ -209,6 +214,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.7.0"
+  equatable:
+    dependency: transitive
+    description:
+      name: equatable
+      sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.5"
   fake_async:
     dependency: transitive
     description:
@@ -241,6 +254,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.1.0"
+  fl_chart:
+    dependency: "direct main"
+    description:
+      name: fl_chart
+      sha256: d0f0d49112f2f4b192481c16d05b6418bd7820e021e265a3c22db98acf7ed7fb
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.68.0"
   flutter:
     dependency: "direct main"
     description: flutter
@@ -258,10 +279,10 @@ packages:
     dependency: "direct dev"
     description:
       name: flutter_lints
-      sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
+      sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.2"
+    version: "4.0.0"
   flutter_test:
     dependency: "direct dev"
     description: flutter
@@ -292,10 +313,10 @@ packages:
     dependency: "direct main"
     description:
       name: gql
-      sha256: afe032332ddfa69b79f1dea2ad7d95923d4993c1b269b224fc7bb3d17e32d33c
+      sha256: "8ecd3585bb9e40d671aa58f52575d950670f99e5ffab18e2b34a757e071a6693"
       url: "https://pub.dev"
     source: hosted
-    version: "1.0.1-alpha+1709845491443"
+    version: "1.0.1-alpha+1717789143880"
   gql_code_builder:
     dependency: transitive
     description:
@@ -308,10 +329,10 @@ packages:
     dependency: transitive
     description:
       name: gql_dedupe_link
-      sha256: "2971173c68623d5c43f5327ea899bd2ee64ce3461c1263f240b4bb6211f667be"
+      sha256: "10bee0564d67c24e0c8bd08bd56e0682b64a135e58afabbeed30d85d5e9fea96"
       url: "https://pub.dev"
     source: hosted
-    version: "2.0.4-alpha+1709845491527"
+    version: "2.0.4-alpha+1715521079596"
   gql_error_link:
     dependency: transitive
     description:
@@ -332,18 +353,18 @@ packages:
     dependency: transitive
     description:
       name: gql_http_link
-      sha256: "1f922eed1b7078fdbfd602187663026f9f659fe9a9499e2207b5d5e01617f658"
+      sha256: ef6ad24d31beb5a30113e9b919eec20876903cc4b0ee0d31550047aaaba7d5dd
       url: "https://pub.dev"
     source: hosted
-    version: "1.0.1+1"
+    version: "1.1.0"
   gql_link:
     dependency: transitive
     description:
       name: gql_link
-      sha256: "177500e250b3742d6d2673d57961e8413b6593dc6bd6a512c51865b6cf096f7e"
+      sha256: "70fd5b5cbcc50601679f4b9fea3bcc994e583f59cfec7e1fec11113074b1a565"
       url: "https://pub.dev"
     source: hosted
-    version: "1.0.1-alpha+1709845491457"
+    version: "1.0.1-alpha+1717789143896"
   gql_transform_link:
     dependency: transitive
     description:
@@ -364,10 +385,10 @@ packages:
     dependency: "direct main"
     description:
       name: graphql
-      sha256: d066e53446166c12537458386b507f7426f2b8801ebafc184576aab3cbc64d56
+      sha256: "62f31433ba194eda7b81a812a83c3d9560766cec5ac0210ea4a3e677c91b8df4"
       url: "https://pub.dev"
     source: hosted
-    version: "5.2.0-beta.7"
+    version: "5.2.0-beta.8"
   graphql_codegen:
     dependency: "direct dev"
     description:
@@ -380,10 +401,10 @@ packages:
     dependency: "direct main"
     description:
       name: graphql_flutter
-      sha256: "39b5e830bc654ab02c5b776c31675841d1a8c95840fdd284efba713b1d47e65d"
+      sha256: "2423b394465e7d83a5e708cd2f5b37b54e7ae9900abfbf0948d512fa46961acb"
       url: "https://pub.dev"
     source: hosted
-    version: "5.2.0-beta.6"
+    version: "5.2.0-beta.7"
   graphs:
     dependency: transitive
     description:
@@ -404,10 +425,10 @@ packages:
     dependency: transitive
     description:
       name: http
-      sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
+      sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
       url: "https://pub.dev"
     source: hosted
-    version: "1.2.1"
+    version: "1.2.2"
   http_multi_server:
     dependency: transitive
     description:
@@ -436,34 +457,34 @@ packages:
     dependency: transitive
     description:
       name: js
-      sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
+      sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
       url: "https://pub.dev"
     source: hosted
-    version: "0.6.7"
+    version: "0.7.1"
   json_annotation:
     dependency: transitive
     description:
       name: json_annotation
-      sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
+      sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
       url: "https://pub.dev"
     source: hosted
-    version: "4.8.1"
+    version: "4.9.0"
   leak_tracker:
     dependency: transitive
     description:
       name: leak_tracker
-      sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
+      sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
       url: "https://pub.dev"
     source: hosted
-    version: "10.0.4"
+    version: "10.0.5"
   leak_tracker_flutter_testing:
     dependency: transitive
     description:
       name: leak_tracker_flutter_testing
-      sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
+      sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.3"
+    version: "3.0.5"
   leak_tracker_testing:
     dependency: transitive
     description:
@@ -476,10 +497,10 @@ packages:
     dependency: transitive
     description:
       name: lints
-      sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
+      sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.0"
+    version: "4.0.0"
   logging:
     dependency: transitive
     description:
@@ -488,6 +509,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.2.0"
+  macros:
+    dependency: transitive
+    description:
+      name: macros
+      sha256: a8403c89b36483b4cbf9f1fcd24562f483cb34a5c9bf101cf2b0d8a083cf1239
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.1.0-main.5"
   matcher:
     dependency: transitive
     description:
@@ -500,18 +529,18 @@ packages:
     dependency: transitive
     description:
       name: material_color_utilities
-      sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+      sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
       url: "https://pub.dev"
     source: hosted
-    version: "0.8.0"
+    version: "0.11.1"
   meta:
     dependency: transitive
     description:
       name: meta
-      sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
+      sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b"
       url: "https://pub.dev"
     source: hosted
-    version: "1.12.0"
+    version: "1.14.0"
   mime:
     dependency: transitive
     description:
@@ -572,18 +601,18 @@ packages:
     dependency: transitive
     description:
       name: path_provider_android
-      sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
+      sha256: "30c5aa827a6ae95ce2853cdc5fe3971daaac00f6f081c419c013f7f57bff2f5e"
       url: "https://pub.dev"
     source: hosted
-    version: "2.2.4"
+    version: "2.2.7"
   path_provider_foundation:
     dependency: transitive
     description:
       name: path_provider_foundation
-      sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
+      sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
       url: "https://pub.dev"
     source: hosted
-    version: "2.3.2"
+    version: "2.4.0"
   path_provider_linux:
     dependency: transitive
     description:
@@ -604,10 +633,10 @@ packages:
     dependency: transitive
     description:
       name: path_provider_windows
-      sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
+      sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
       url: "https://pub.dev"
     source: hosted
-    version: "2.2.1"
+    version: "2.3.0"
   petitparser:
     dependency: transitive
     description:
@@ -620,10 +649,10 @@ packages:
     dependency: transitive
     description:
       name: platform
-      sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
+      sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
       url: "https://pub.dev"
     source: hosted
-    version: "3.1.4"
+    version: "3.1.5"
   plugin_platform_interface:
     dependency: transitive
     description:
@@ -660,10 +689,10 @@ packages:
     dependency: transitive
     description:
       name: pubspec_parse
-      sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
+      sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
       url: "https://pub.dev"
     source: hosted
-    version: "1.2.3"
+    version: "1.3.0"
   recase:
     dependency: transitive
     description:
@@ -692,10 +721,10 @@ packages:
     dependency: transitive
     description:
       name: shelf_web_socket
-      sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
+      sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
       url: "https://pub.dev"
     source: hosted
-    version: "1.0.4"
+    version: "2.0.0"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -761,10 +790,10 @@ packages:
     dependency: transitive
     description:
       name: test_api
-      sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
+      sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794"
       url: "https://pub.dev"
     source: hosted
-    version: "0.7.0"
+    version: "0.7.1"
   timing:
     dependency: transitive
     description:
@@ -785,10 +814,10 @@ packages:
     dependency: transitive
     description:
       name: uuid
-      sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8"
+      sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
       url: "https://pub.dev"
     source: hosted
-    version: "4.4.0"
+    version: "4.4.2"
   vector_math:
     dependency: transitive
     description:
@@ -801,10 +830,10 @@ packages:
     dependency: transitive
     description:
       name: vm_service
-      sha256: a75f83f14ad81d5fe4b3319710b90dec37da0e22612326b696c9e1b8f34bbf48
+      sha256: "7475cb4dd713d57b6f7464c0e13f06da0d535d8b2067e188962a59bac2cf280b"
       url: "https://pub.dev"
     source: hosted
-    version: "14.2.0"
+    version: "14.2.2"
   watcher:
     dependency: transitive
     description:
@@ -825,18 +854,10 @@ packages:
     dependency: transitive
     description:
       name: web_socket_channel
-      sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
+      sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
       url: "https://pub.dev"
     source: hosted
-    version: "2.4.5"
-  win32:
-    dependency: transitive
-    description:
-      name: win32
-      sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a"
-      url: "https://pub.dev"
-    source: hosted
-    version: "5.4.0"
+    version: "2.4.0"
   xdg_directories:
     dependency: transitive
     description:
@@ -862,5 +883,5 @@ packages:
     source: hosted
     version: "3.1.2"
 sdks:
-  dart: ">=3.4.0-282.1.beta <4.0.0"
-  flutter: ">=3.18.0-18.0.pre.54"
+  dart: ">=3.4.0 <4.0.0"
+  flutter: ">=3.22.0"
diff --git a/ui/pubspec.yaml b/ui/pubspec.yaml
index fda9fa5..fc1e4d5 100644
--- a/ui/pubspec.yaml
+++ b/ui/pubspec.yaml
@@ -38,11 +38,12 @@ dependencies:
 
   path: any
   dynamic_color: ^1.7.0
+  fl_chart: ^0.68.0
 dev_dependencies:
   flutter_test:
     sdk: flutter
 
-  flutter_lints: ^3.0.0
+  flutter_lints: ^4.0.0
   build_runner: ^2.4.9
   graphql_codegen: ^0.14.0