rename and delete in root fs fix
Some checks failed
docker / build-docker (push) Failing after 1m33s
Some checks failed
docker / build-docker (push) Failing after 1m33s
This commit is contained in:
parent
80884aca6a
commit
63e63c1c37
18 changed files with 282 additions and 58 deletions
|
@ -11,14 +11,12 @@ import (
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/ctxbilly"
|
"git.kmsign.ru/royalcat/tstor/pkg/ctxbilly"
|
||||||
wnfs "git.kmsign.ru/royalcat/tstor/pkg/go-nfs"
|
wnfs "git.kmsign.ru/royalcat/tstor/pkg/go-nfs"
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/config"
|
"git.kmsign.ru/royalcat/tstor/src/config"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/delivery"
|
|
||||||
"git.kmsign.ru/royalcat/tstor/src/sources"
|
"git.kmsign.ru/royalcat/tstor/src/sources"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/sources/qbittorrent"
|
"git.kmsign.ru/royalcat/tstor/src/sources/qbittorrent"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/sources/ytdlp"
|
"git.kmsign.ru/royalcat/tstor/src/sources/ytdlp"
|
||||||
|
@ -189,15 +187,6 @@ func run(configPath string) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
|
||||||
logFilename := filepath.Join(conf.Log.Path, "logs")
|
|
||||||
|
|
||||||
err := delivery.Run(nil, sfs, logFilename, conf)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(ctx, "error initializing HTTP server", rlog.Error(err))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(sigChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-sigChan
|
<-sigChan
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -2,6 +2,8 @@ module git.kmsign.ru/royalcat/tstor
|
||||||
|
|
||||||
go 1.22.3
|
go 1.22.3
|
||||||
|
|
||||||
|
replace github.com/iceber/iouring-go => github.com/royalcat/iouring-go v0.0.0-20240925200811-286062ac1b23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/gqlgen v0.17.49
|
github.com/99designs/gqlgen v0.17.49
|
||||||
github.com/agoda-com/opentelemetry-go/otelslog v0.1.1
|
github.com/agoda-com/opentelemetry-go/otelslog v0.1.1
|
||||||
|
@ -21,6 +23,7 @@ require (
|
||||||
github.com/grafana/otel-profiling-go v0.5.1
|
github.com/grafana/otel-profiling-go v0.5.1
|
||||||
github.com/grafana/pyroscope-go v1.1.2
|
github.com/grafana/pyroscope-go v1.1.2
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
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/parsers/yaml v0.1.0
|
||||||
github.com/knadh/koanf/providers/env v0.1.0
|
github.com/knadh/koanf/providers/env v0.1.0
|
||||||
github.com/knadh/koanf/providers/file v0.1.0
|
github.com/knadh/koanf/providers/file v0.1.0
|
||||||
|
|
3
go.sum
3
go.sum
|
@ -528,6 +528,8 @@ github.com/royalcat/ctxio v0.0.0-20240602060200-590d464c39be h1:Ui+Imq1Vk26rfpkL
|
||||||
github.com/royalcat/ctxio v0.0.0-20240602060200-590d464c39be/go.mod h1:NFNp3OsEMUPYj5LZUFDiyDt+2E6gR/g8JLd0k+y8XWI=
|
github.com/royalcat/ctxio v0.0.0-20240602060200-590d464c39be/go.mod h1:NFNp3OsEMUPYj5LZUFDiyDt+2E6gR/g8JLd0k+y8XWI=
|
||||||
github.com/royalcat/ctxprogress v0.0.0-20240614113930-3cc5bb935bff h1:KlZaOEZYhCzyNYIp0LcE7MNR2Ar0PJS3eJU6A5mMTpk=
|
github.com/royalcat/ctxprogress v0.0.0-20240614113930-3cc5bb935bff h1:KlZaOEZYhCzyNYIp0LcE7MNR2Ar0PJS3eJU6A5mMTpk=
|
||||||
github.com/royalcat/ctxprogress v0.0.0-20240614113930-3cc5bb935bff/go.mod h1:RcUpbosy/m3bJ3JsVO18MXEbrKRHOHkmYBXigDGekaA=
|
github.com/royalcat/ctxprogress v0.0.0-20240614113930-3cc5bb935bff/go.mod h1:RcUpbosy/m3bJ3JsVO18MXEbrKRHOHkmYBXigDGekaA=
|
||||||
|
github.com/royalcat/iouring-go v0.0.0-20240925200811-286062ac1b23 h1:3yOlLKYd6iSGkRUOCPuBQibjjvZyrGB/4sm0fh3nNuQ=
|
||||||
|
github.com/royalcat/iouring-go v0.0.0-20240925200811-286062ac1b23/go.mod h1:LEzdaZarZ5aqROlLIwJ4P7h3+4o71008fSy6wpaEB+s=
|
||||||
github.com/royalcat/kv v0.0.0-20240707205211-fedd4883af85 h1:AAuCp03M23u4qrK3dT1afFgf+diEijvSFnHb93Lv3PY=
|
github.com/royalcat/kv v0.0.0-20240707205211-fedd4883af85 h1:AAuCp03M23u4qrK3dT1afFgf+diEijvSFnHb93Lv3PY=
|
||||||
github.com/royalcat/kv v0.0.0-20240707205211-fedd4883af85/go.mod h1:UB/VwpTut8c3IXLJFvYWFxAAZymk9eBuJRMJmpSpwYU=
|
github.com/royalcat/kv v0.0.0-20240707205211-fedd4883af85/go.mod h1:UB/VwpTut8c3IXLJFvYWFxAAZymk9eBuJRMJmpSpwYU=
|
||||||
github.com/royalcat/kv/kvbadger v0.0.0-20240707205211-fedd4883af85 h1:OXRYz+eDPAlQjE1UCSIoBzVHjQ3Ayx7fGSM/Zlo3bhI=
|
github.com/royalcat/kv/kvbadger v0.0.0-20240707205211-fedd4883af85 h1:OXRYz+eDPAlQjE1UCSIoBzVHjQ3Ayx7fGSM/Zlo3bhI=
|
||||||
|
@ -782,6 +784,7 @@ 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-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-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-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-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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
112
pkg/uring/file.go
Normal file
112
pkg/uring/file.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package uring
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/iceber/iouring-go"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tracer = otel.Tracer("github.com/royalcat/tstor/pkg/uring")
|
||||||
|
|
||||||
|
type FS struct {
|
||||||
|
ur *iouring.IOURing
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFS(ur *iouring.IOURing) *FS {
|
||||||
|
return &FS{
|
||||||
|
ur: ur,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *FS) OpenFile(ctx context.Context, name string) (File, error) {
|
||||||
|
ctx, span := tracer.Start(ctx, "uring.FS.OpenFile", trace.WithAttributes(attribute.String("name", name)))
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return File{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return File{
|
||||||
|
ur: o.ur,
|
||||||
|
f: f,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFile(ur *iouring.IOURing, f *os.File) *File {
|
||||||
|
return &File{
|
||||||
|
ur: ur,
|
||||||
|
f: f,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
ur *iouring.IOURing
|
||||||
|
f *os.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *File) pread(ctx context.Context, b []byte, off uint64) (int, error) {
|
||||||
|
ctx, span := tracer.Start(ctx, "uring.File.pread", trace.WithAttributes(attribute.Int("size", len(b))))
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
req, err := o.ur.Pread(o.f, b, off, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-req.Done():
|
||||||
|
return req.GetRes()
|
||||||
|
case <-ctx.Done():
|
||||||
|
if _, err := req.Cancel(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
<-req.Done()
|
||||||
|
return 0, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) ReadAt(ctx context.Context, b []byte, off int64) (n int, err error) {
|
||||||
|
ctx, span := tracer.Start(ctx, "uring.File.ReadAt", trace.WithAttributes(attribute.Int("size", len(b))))
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
return f.f.ReadAt(b, off)
|
||||||
|
|
||||||
|
for len(b) > 0 {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
err = ctx.Err()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
m, e := f.pread(ctx, b, uint64(off))
|
||||||
|
if e != nil {
|
||||||
|
err = e
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n += m
|
||||||
|
b = b[m:]
|
||||||
|
off += int64(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *File) Close(ctx context.Context) error {
|
||||||
|
return o.f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitRequest(ctx context.Context, req iouring.Request) (int, error) {
|
||||||
|
select {
|
||||||
|
case <-req.Done():
|
||||||
|
return req.GetRes()
|
||||||
|
case <-ctx.Done():
|
||||||
|
if _, err := req.Cancel(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return 0, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
|
@ -118,8 +118,8 @@ func (bfs *fsWrapper) Remove(ctx context.Context, filename string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename implements billy.Filesystem.
|
// Rename implements billy.Filesystem.
|
||||||
func (*fsWrapper) Rename(ctx context.Context, oldpath string, newpath string) error {
|
func (bfs *fsWrapper) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
return billy.ErrNotSupported
|
return bfs.fs.Rename(ctx, oldpath, newpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root implements billy.Filesystem.
|
// Root implements billy.Filesystem.
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/anacrolix/torrent/metainfo"
|
"github.com/anacrolix/torrent/metainfo"
|
||||||
"github.com/anacrolix/torrent/types/infohash"
|
"github.com/anacrolix/torrent/types/infohash"
|
||||||
infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
|
infohash_v2 "github.com/anacrolix/torrent/types/infohash-v2"
|
||||||
|
"github.com/iceber/iouring-go"
|
||||||
"github.com/royalcat/ctxio"
|
"github.com/royalcat/ctxio"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
)
|
)
|
||||||
|
@ -30,6 +31,7 @@ type Daemon struct {
|
||||||
qb qbittorrent.Client
|
qb qbittorrent.Client
|
||||||
client *cacheClient
|
client *cacheClient
|
||||||
dataDir string
|
dataDir string
|
||||||
|
ur *iouring.IOURing
|
||||||
log *rlog.Logger
|
log *rlog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +92,8 @@ func NewDaemon(conf config.QBittorrent) (*Daemon, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for { // wait for qbittorrent to start
|
for { // wait for qbittorrent to start
|
||||||
_, err = qb.Application().Version(ctx)
|
ver, err := qb.Application().Version(ctx)
|
||||||
|
log.Info(ctx, "qbittorrent started", slog.String("version", ver))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -110,10 +113,16 @@ func NewDaemon(conf config.QBittorrent) (*Daemon, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ur, err := iouring.New(8, iouring.WithAsync())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &Daemon{
|
return &Daemon{
|
||||||
qb: qb,
|
qb: qb,
|
||||||
proc: proc,
|
proc: proc,
|
||||||
dataDir: conf.DataFolder,
|
dataDir: conf.DataFolder,
|
||||||
|
ur: ur,
|
||||||
client: wrapClient(qb),
|
client: wrapClient(qb),
|
||||||
log: rlog.Component("qbittorrent"),
|
log: rlog.Component("qbittorrent"),
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -162,7 +171,7 @@ func (fs *Daemon) GetTorrentFS(ctx context.Context, file vfs.File) (vfs.Filesyst
|
||||||
return nil, fmt.Errorf("error syncing torrent state: %w", err)
|
return nil, fmt.Errorf("error syncing torrent state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newTorrentFS(ctx, fs.client, file.Name(), ih.HexString(), torrentPath)
|
return newTorrentFS(ctx, fs.ur, fs.client, file.Name(), ih.HexString(), torrentPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Daemon) syncTorrentState(ctx context.Context, file vfs.File, ih metainfo.Hash, torrentPath string) error {
|
func (d *Daemon) syncTorrentState(ctx context.Context, file vfs.File, ih metainfo.Hash, torrentPath string) error {
|
||||||
|
|
|
@ -15,7 +15,9 @@ import (
|
||||||
|
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/qbittorrent"
|
"git.kmsign.ru/royalcat/tstor/pkg/qbittorrent"
|
||||||
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
||||||
|
"git.kmsign.ru/royalcat/tstor/pkg/uring"
|
||||||
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
"git.kmsign.ru/royalcat/tstor/src/vfs"
|
||||||
|
"github.com/iceber/iouring-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FS struct {
|
type FS struct {
|
||||||
|
@ -25,6 +27,8 @@ type FS struct {
|
||||||
hash string
|
hash string
|
||||||
dataDir string // directory where torrent files are stored
|
dataDir string // directory where torrent files are stored
|
||||||
|
|
||||||
|
ur *iouring.IOURing
|
||||||
|
|
||||||
entries map[string]fileEntry
|
entries map[string]fileEntry
|
||||||
|
|
||||||
log *rlog.Logger
|
log *rlog.Logger
|
||||||
|
@ -39,7 +43,7 @@ type fileEntry struct {
|
||||||
|
|
||||||
var _ vfs.Filesystem = (*FS)(nil)
|
var _ vfs.Filesystem = (*FS)(nil)
|
||||||
|
|
||||||
func newTorrentFS(ctx context.Context, client *cacheClient, name string, hash string, dataDir string) (*FS, error) {
|
func newTorrentFS(ctx context.Context, ur *iouring.IOURing, client *cacheClient, name string, hash string, dataDir string) (*FS, error) {
|
||||||
ctx, span := trace.Start(ctx, "newTorrentFS")
|
ctx, span := trace.Start(ctx, "newTorrentFS")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
|
@ -69,6 +73,8 @@ func newTorrentFS(ctx context.Context, client *cacheClient, name string, hash st
|
||||||
|
|
||||||
entries: entries,
|
entries: entries,
|
||||||
|
|
||||||
|
ur: ur,
|
||||||
|
|
||||||
log: rlog.Component("qbittorrent", "fs"),
|
log: rlog.Component("qbittorrent", "fs"),
|
||||||
|
|
||||||
FilesystemPrototype: vfs.FilesystemPrototype(name),
|
FilesystemPrototype: vfs.FilesystemPrototype(name),
|
||||||
|
@ -82,7 +88,7 @@ func (f *FS) Open(ctx context.Context, name string) (vfs.File, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry, ok := f.entries[name]; ok {
|
if entry, ok := f.entries[name]; ok {
|
||||||
return openFile(ctx, f.client, f.dataDir, f.hash, entry.Content)
|
return openFile(ctx, f.ur, f.client, f.dataDir, f.hash, entry.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
for p := range f.entries {
|
for p := range f.entries {
|
||||||
|
@ -144,6 +150,27 @@ func (f *FS) Unlink(ctx context.Context, filename string) error {
|
||||||
return vfs.ErrNotExist
|
return vfs.ErrNotExist
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FS) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
oldpath = vfs.AbsPath(path.Clean(oldpath))
|
||||||
|
newpath = vfs.AbsPath(path.Clean(newpath))
|
||||||
|
|
||||||
|
if _, ok := f.entries[oldpath]; ok {
|
||||||
|
err := f.client.qb.Torrent().RenameFile(ctx, f.hash, vfs.RelPath(oldpath), vfs.RelPath(newpath))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to rename file %s to %s: %w", oldpath, newpath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
|
||||||
|
f.entries[newpath] = f.entries[oldpath]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return vfs.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FS) removeFile(ctx context.Context, hash string, content *qbittorrent.TorrentContent) error {
|
func (f *FS) removeFile(ctx context.Context, hash string, content *qbittorrent.TorrentContent) error {
|
||||||
log := f.log.With(slog.String("hash", hash), slog.String("file", content.Name))
|
log := f.log.With(slog.String("hash", hash), slog.String("file", content.Name))
|
||||||
|
|
||||||
|
@ -170,12 +197,17 @@ func (f *FS) removeFile(ctx context.Context, hash string, content *qbittorrent.T
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openFile(ctx context.Context, client *cacheClient, torrentDir string, hash string, content *qbittorrent.TorrentContent) (*File, error) {
|
func openFile(ctx context.Context, ur *iouring.IOURing, client *cacheClient, torrentDir string, hash string, content *qbittorrent.TorrentContent) (*File, error) {
|
||||||
props, err := client.getProperties(ctx, hash)
|
props, err := client.getProperties(ctx, hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(path.Join(torrentDir, content.Name), os.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &File{
|
return &File{
|
||||||
client: client,
|
client: client,
|
||||||
hash: hash,
|
hash: hash,
|
||||||
|
@ -186,6 +218,8 @@ func openFile(ctx context.Context, client *cacheClient, torrentDir string, hash
|
||||||
pieceSize: props.PieceSize,
|
pieceSize: props.PieceSize,
|
||||||
fileSize: content.Size,
|
fileSize: content.Size,
|
||||||
|
|
||||||
|
file: uring.NewFile(ur, file),
|
||||||
|
|
||||||
offset: 0,
|
offset: 0,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -199,8 +233,9 @@ type File struct {
|
||||||
pieceSize int
|
pieceSize int
|
||||||
fileSize int64
|
fileSize int64
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
file *uring.File
|
||||||
offset int64
|
offset int64
|
||||||
osfile *os.File
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ vfs.File = (*File)(nil)
|
var _ vfs.File = (*File)(nil)
|
||||||
|
@ -298,33 +333,26 @@ func (f *File) waitPieceAvailable(ctx context.Context, offset int64, size int) e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read implements vfs.File.
|
// Read implements vfs.File.
|
||||||
func (f *File) Read(ctx context.Context, p []byte) (n int, err error) {
|
func (f *File) Read(ctx context.Context, p []byte) (int, error) {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
|
||||||
if err := f.waitPieceAvailable(ctx, f.offset, len(p)); err != nil {
|
if err := f.waitPieceAvailable(ctx, f.offset, len(p)); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptor, err := f.descriptor()
|
n, err := f.file.ReadAt(ctx, p, f.offset)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = descriptor.ReadAt(p, f.offset)
|
|
||||||
f.offset += int64(n)
|
f.offset += int64(n)
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadAt implements vfs.File.
|
// ReadAt implements vfs.File.
|
||||||
func (f *File) ReadAt(ctx context.Context, p []byte, off int64) (n int, err error) {
|
func (f *File) ReadAt(ctx context.Context, p []byte, off int64) (int, error) {
|
||||||
if err := f.waitPieceAvailable(ctx, f.offset, len(p)); err != nil {
|
if err := f.waitPieceAvailable(ctx, f.offset, len(p)); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptor, err := f.descriptor()
|
return f.file.ReadAt(ctx, p, off)
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return descriptor.ReadAt(p, off)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size implements vfs.File.
|
// Size implements vfs.File.
|
||||||
|
@ -337,28 +365,9 @@ func (f *File) Type() fs.FileMode {
|
||||||
return fs.ModeDir
|
return fs.ModeDir
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *File) descriptor() (*os.File, error) {
|
|
||||||
if f.osfile != nil {
|
|
||||||
return f.osfile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
osfile, err := os.Open(path.Join(f.torrentDir, f.filePath))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.osfile = osfile
|
|
||||||
|
|
||||||
return f.osfile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements vfs.File.
|
// Close implements vfs.File.
|
||||||
func (f *File) Close(ctx context.Context) error {
|
func (f *File) Close(ctx context.Context) error {
|
||||||
if f.osfile != nil {
|
return f.file.Close(ctx)
|
||||||
err := f.osfile.Close()
|
|
||||||
f.osfile = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type fileInfo struct {
|
type fileInfo struct {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -85,10 +86,13 @@ func downloadLatestQbitRelease(ctx context.Context, binPath string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return downloadFile(binPath, downloadUrl)
|
|
||||||
|
slog.InfoContext(ctx, "downloading latest qbittorrent-nox release", slog.String("url", downloadUrl))
|
||||||
|
|
||||||
|
return downloadFile(ctx, binPath, downloadUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func downloadFile(filepath string, webUrl string) error {
|
func downloadFile(ctx context.Context, filepath string, webUrl string) error {
|
||||||
if stat, err := os.Stat(filepath); err == nil {
|
if stat, err := os.Stat(filepath); err == nil {
|
||||||
resp, err := http.Head(webUrl)
|
resp, err := http.Head(webUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -107,6 +111,7 @@ func downloadFile(filepath string, webUrl string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.ContentLength == stat.Size() && lastModified.Before(stat.ModTime()) {
|
if resp.ContentLength == stat.Size() && lastModified.Before(stat.ModTime()) {
|
||||||
|
slog.InfoContext(ctx, "there is already newest version of the file", slog.String("filepath", filepath))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,8 +123,13 @@ func downloadFile(filepath string, webUrl string) error {
|
||||||
}
|
}
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, webUrl, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Get the data
|
// Get the data
|
||||||
resp, err := http.Get(webUrl)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -383,6 +383,11 @@ func (fs *TorrentFS) Unlink(ctx context.Context, name string) error {
|
||||||
return fs.Torrent.ExcludeFile(ctx, tfile.file)
|
return fs.Torrent.ExcludeFile(ctx, tfile.file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename implements vfs.Filesystem.
|
||||||
|
func (s *TorrentFS) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
return vfs.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
var _ vfs.File = (*torrentFile)(nil)
|
var _ vfs.File = (*torrentFile)(nil)
|
||||||
|
|
||||||
type torrentFile struct {
|
type torrentFile struct {
|
||||||
|
|
|
@ -68,3 +68,8 @@ func (s *SourceFS) Stat(ctx context.Context, filename string) (fs.FileInfo, erro
|
||||||
func (s *SourceFS) Unlink(ctx context.Context, filename string) error {
|
func (s *SourceFS) Unlink(ctx context.Context, filename string) error {
|
||||||
return vfs.ErrNotImplemented
|
return vfs.ErrNotImplemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename implements vfs.Filesystem.
|
||||||
|
func (s *SourceFS) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
return vfs.ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
|
@ -54,6 +54,11 @@ type ArchiveFS struct {
|
||||||
files map[string]File
|
files map[string]File
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename implements Filesystem.
|
||||||
|
func (a *ArchiveFS) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
// ModTime implements Filesystem.
|
// ModTime implements Filesystem.
|
||||||
func (a *ArchiveFS) ModTime() time.Time {
|
func (a *ArchiveFS) ModTime() time.Time {
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
|
@ -257,6 +262,9 @@ func (d *archiveFile) loadMore(ctx context.Context, to int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *archiveFile) Read(ctx context.Context, p []byte) (n int, err error) {
|
func (d *archiveFile) Read(ctx context.Context, p []byte) (n int, err error) {
|
||||||
|
ctx, span := tracer.Start(ctx, "archive.File.Read")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
d.m.Lock()
|
d.m.Lock()
|
||||||
defer d.m.Unlock()
|
defer d.m.Unlock()
|
||||||
|
|
||||||
|
|
|
@ -65,6 +65,11 @@ func (c *CtxBillyFs) ReadDir(ctx context.Context, path string) ([]fs.DirEntry, e
|
||||||
return entries, nil
|
return entries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename implements Filesystem.
|
||||||
|
func (c *CtxBillyFs) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
return c.fs.Rename(ctx, oldpath, newpath)
|
||||||
|
}
|
||||||
|
|
||||||
type infoEntry struct {
|
type infoEntry struct {
|
||||||
fs.FileInfo
|
fs.FileInfo
|
||||||
}
|
}
|
||||||
|
@ -93,7 +98,7 @@ func (c *CtxBillyFs) Type() fs.FileMode {
|
||||||
|
|
||||||
// Unlink implements Filesystem.
|
// Unlink implements Filesystem.
|
||||||
func (c *CtxBillyFs) Unlink(ctx context.Context, filename string) error {
|
func (c *CtxBillyFs) Unlink(ctx context.Context, filename string) error {
|
||||||
return fs.ErrInvalid
|
return c.fs.Remove(ctx, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCtxBillyFile(info fs.FileInfo, bf ctxbilly.File) *CtxBillyFile {
|
func NewCtxBillyFile(info fs.FileInfo, bf ctxbilly.File) *CtxBillyFile {
|
||||||
|
|
|
@ -14,6 +14,11 @@ type DummyFs struct {
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename implements Filesystem.
|
||||||
|
func (d *DummyFs) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
// ModTime implements Filesystem.
|
// ModTime implements Filesystem.
|
||||||
func (d *DummyFs) ModTime() time.Time {
|
func (d *DummyFs) ModTime() time.Time {
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
|
|
|
@ -37,6 +37,8 @@ type Filesystem interface {
|
||||||
Stat(ctx context.Context, filename string) (fs.FileInfo, error)
|
Stat(ctx context.Context, filename string) (fs.FileInfo, error)
|
||||||
Unlink(ctx context.Context, filename string) error
|
Unlink(ctx context.Context, filename string) error
|
||||||
|
|
||||||
|
Rename(ctx context.Context, oldpath, newpath string) error
|
||||||
|
|
||||||
// As filesystem mounted to some path, make sense to have the filesystem implement DirEntry
|
// As filesystem mounted to some path, make sense to have the filesystem implement DirEntry
|
||||||
fs.DirEntry
|
fs.DirEntry
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,6 +202,25 @@ func (fs *LogFS) Unlink(ctx context.Context, filename string) (err error) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename implements Filesystem.
|
||||||
|
func (lfs *LogFS) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, lfs.timeout)
|
||||||
|
defer cancel()
|
||||||
|
ctx, span := tracer.Start(ctx, "Rename",
|
||||||
|
lfs.traceAttrs(
|
||||||
|
attribute.String("oldpath", oldpath),
|
||||||
|
attribute.String("newpath", newpath),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
err := lfs.fs.Rename(ctx, oldpath, newpath)
|
||||||
|
if isLoggableError(err) {
|
||||||
|
lfs.log.Error(ctx, "Failed to rename", rlog.Error(err))
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
type LogFile struct {
|
type LogFile struct {
|
||||||
filename string
|
filename string
|
||||||
f File
|
f File
|
||||||
|
|
|
@ -60,6 +60,11 @@ func (mfs *MemoryFs) Type() fs.FileMode {
|
||||||
return fs.ModeDir
|
return fs.ModeDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename implements Filesystem.
|
||||||
|
func (mfs *MemoryFs) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
return ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
func NewMemoryFS(name string, files map[string]*MemoryFile) *MemoryFs {
|
func NewMemoryFS(name string, files map[string]*MemoryFile) *MemoryFs {
|
||||||
return &MemoryFs{
|
return &MemoryFs{
|
||||||
name: name,
|
name: name,
|
||||||
|
|
|
@ -2,6 +2,7 @@ package vfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -29,7 +30,11 @@ func (fs *OsFS) Stat(ctx context.Context, filename string) (fs.FileInfo, error)
|
||||||
|
|
||||||
// Unlink implements Filesystem.
|
// Unlink implements Filesystem.
|
||||||
func (fs *OsFS) Unlink(ctx context.Context, filename string) error {
|
func (fs *OsFS) Unlink(ctx context.Context, filename string) error {
|
||||||
return os.RemoveAll(path.Join(fs.hostDir, filename))
|
err := os.Remove(path.Join(fs.hostDir, filename))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to remove %s: %w", filename, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open implements Filesystem.
|
// Open implements Filesystem.
|
||||||
|
@ -41,6 +46,11 @@ func (fs *OsFS) Open(ctx context.Context, filename string) (File, error) {
|
||||||
return NewLazyOsFile(path.Join(fs.hostDir, filename))
|
return NewLazyOsFile(path.Join(fs.hostDir, filename))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename implements Filesystem.
|
||||||
|
func (fs *OsFS) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
return os.Rename(path.Join(fs.hostDir, oldpath), path.Join(fs.hostDir, newpath))
|
||||||
|
}
|
||||||
|
|
||||||
// ReadDir implements Filesystem.
|
// ReadDir implements Filesystem.
|
||||||
func (o *OsFS) ReadDir(ctx context.Context, dir string) ([]fs.DirEntry, error) {
|
func (o *OsFS) ReadDir(ctx context.Context, dir string) ([]fs.DirEntry, error) {
|
||||||
return os.ReadDir(path.Join(o.hostDir, dir))
|
return os.ReadDir(path.Join(o.hostDir, dir))
|
||||||
|
|
|
@ -175,6 +175,31 @@ func (r *ResolverFS) Unlink(ctx context.Context, filename string) error {
|
||||||
return r.rootFS.Unlink(ctx, fsPath)
|
return r.rootFS.Unlink(ctx, fsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rename implements Filesystem.
|
||||||
|
func (r *ResolverFS) Rename(ctx context.Context, oldpath string, newpath string) error {
|
||||||
|
oldFsPath, oldNestedFs, oldNestedFsPath, err := r.resolver.ResolvePath(ctx, oldpath, r.rootFS.Open)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newFsPath, newNestedFs, newNestedFsPath, err := r.resolver.ResolvePath(ctx, newpath, r.rootFS.Open)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldNestedFs == nil && newNestedFs == nil {
|
||||||
|
return r.rootFS.Rename(ctx, oldFsPath, newFsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(oldNestedFs)
|
||||||
|
fmt.Println(newNestedFs)
|
||||||
|
|
||||||
|
if oldNestedFs == nil || newNestedFs == nil || oldNestedFs == newNestedFs {
|
||||||
|
return oldNestedFs.Rename(ctx, oldNestedFsPath, newNestedFsPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("rename between different nested filesystems is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// Info implements Filesystem.
|
// Info implements Filesystem.
|
||||||
func (r *ResolverFS) Info() (fs.FileInfo, error) {
|
func (r *ResolverFS) Info() (fs.FileInfo, error) {
|
||||||
return r, nil
|
return r, nil
|
||||||
|
|
Loading…
Reference in a new issue