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 }