229 lines
4.3 KiB
Go
229 lines
4.3 KiB
Go
|
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
|
||
|
}
|