2023-10-16 09:18:40 +00:00
|
|
|
package vfs
|
2020-09-27 19:23:47 +00:00
|
|
|
|
|
|
|
import (
|
2021-11-29 10:07:54 +00:00
|
|
|
"context"
|
|
|
|
"io"
|
2023-12-21 23:15:39 +00:00
|
|
|
"io/fs"
|
|
|
|
"path"
|
2023-12-25 22:11:03 +00:00
|
|
|
"slices"
|
2024-01-01 18:17:32 +00:00
|
|
|
"strings"
|
2021-11-16 12:13:58 +00:00
|
|
|
"sync"
|
2021-11-29 10:07:54 +00:00
|
|
|
"time"
|
2021-11-16 12:13:58 +00:00
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
"git.kmsign.ru/royalcat/tstor/src/host/controller"
|
2023-10-08 16:46:03 +00:00
|
|
|
"git.kmsign.ru/royalcat/tstor/src/iio"
|
2024-03-17 21:00:34 +00:00
|
|
|
"github.com/RoaringBitmap/roaring"
|
2021-12-01 18:59:21 +00:00
|
|
|
"github.com/anacrolix/missinggo/v2"
|
2020-09-27 19:23:47 +00:00
|
|
|
"github.com/anacrolix/torrent"
|
2023-12-25 22:11:03 +00:00
|
|
|
"golang.org/x/exp/maps"
|
2020-09-27 19:23:47 +00:00
|
|
|
)
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
var _ Filesystem = &TorrentFs{}
|
2020-09-27 19:23:47 +00:00
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
type TorrentFs struct {
|
2024-03-19 21:30:37 +00:00
|
|
|
name string
|
|
|
|
|
|
|
|
mu sync.Mutex
|
|
|
|
Torrent *controller.Torrent
|
2023-12-21 23:15:39 +00:00
|
|
|
|
2021-11-29 10:07:54 +00:00
|
|
|
readTimeout int
|
2023-10-16 09:18:40 +00:00
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
filesCache map[string]File
|
2023-12-21 23:15:39 +00:00
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
resolver *resolver
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
|
|
|
|
2024-03-19 21:30:37 +00:00
|
|
|
func NewTorrentFs(name string, c *controller.Torrent, readTimeout int) *TorrentFs {
|
2023-10-16 09:18:40 +00:00
|
|
|
return &TorrentFs{
|
2024-03-19 21:30:37 +00:00
|
|
|
name: name,
|
|
|
|
Torrent: c,
|
2021-11-29 10:07:54 +00:00
|
|
|
readTimeout: readTimeout,
|
2023-10-16 09:18:40 +00:00
|
|
|
resolver: newResolver(ArchiveFactories),
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-19 21:30:37 +00:00
|
|
|
var _ fs.DirEntry = (*TorrentFs)(nil)
|
|
|
|
|
|
|
|
// Name implements fs.DirEntry.
|
|
|
|
func (tfs *TorrentFs) Name() string {
|
|
|
|
return tfs.name
|
|
|
|
}
|
|
|
|
|
|
|
|
// Info implements fs.DirEntry.
|
|
|
|
func (tfs *TorrentFs) Info() (fs.FileInfo, error) {
|
|
|
|
return newDirInfo(tfs.name), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsDir implements fs.DirEntry.
|
|
|
|
func (tfs *TorrentFs) IsDir() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type implements fs.DirEntry.
|
|
|
|
func (tfs *TorrentFs) Type() fs.FileMode {
|
|
|
|
return fs.ModeDir
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
func (fs *TorrentFs) files() (map[string]File, error) {
|
|
|
|
fs.mu.Lock()
|
|
|
|
defer fs.mu.Unlock()
|
2023-12-25 22:11:03 +00:00
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
if fs.filesCache != nil {
|
|
|
|
return fs.filesCache, nil
|
|
|
|
}
|
2023-12-25 22:11:03 +00:00
|
|
|
|
2024-03-19 21:30:37 +00:00
|
|
|
files, err := fs.Torrent.Files(context.Background())
|
2024-01-28 20:22:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-01-01 18:17:32 +00:00
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
fs.filesCache = make(map[string]File)
|
|
|
|
for _, file := range files {
|
2024-03-17 21:00:34 +00:00
|
|
|
file.Download()
|
2024-01-28 20:22:49 +00:00
|
|
|
p := AbsPath(file.Path())
|
2024-01-07 17:09:56 +00:00
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
fs.filesCache[p] = &torrentFile{
|
|
|
|
name: path.Base(p),
|
|
|
|
timeout: fs.readTimeout,
|
|
|
|
file: file,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO optional
|
2024-03-19 21:30:37 +00:00
|
|
|
if len(fs.filesCache) == 1 && fs.resolver.isNestedFs(fs.Torrent.Name()) {
|
|
|
|
filepath := "/" + fs.Torrent.Name()
|
2024-01-28 20:22:49 +00:00
|
|
|
if file, ok := fs.filesCache[filepath]; ok {
|
|
|
|
nestedFs, err := fs.resolver.nestedFs(filepath, file)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if nestedFs == nil {
|
|
|
|
goto DEFAULT_DIR // FIXME
|
|
|
|
}
|
|
|
|
fs.filesCache, err = listFilesRecursive(nestedFs, "/")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2023-12-25 22:11:03 +00:00
|
|
|
}
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
return fs.filesCache, nil
|
|
|
|
}
|
2024-01-01 18:17:32 +00:00
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DEFAULT_DIR:
|
2024-03-19 21:30:37 +00:00
|
|
|
rootDir := "/" + fs.Torrent.Name() + "/"
|
2024-01-28 20:22:49 +00:00
|
|
|
singleDir := true
|
|
|
|
for k, _ := range fs.filesCache {
|
|
|
|
if !strings.HasPrefix(k, rootDir) {
|
|
|
|
singleDir = false
|
2023-10-16 09:18:40 +00:00
|
|
|
}
|
2024-01-28 20:22:49 +00:00
|
|
|
}
|
|
|
|
if singleDir {
|
|
|
|
for k, f := range fs.filesCache {
|
|
|
|
delete(fs.filesCache, k)
|
|
|
|
k, _ = strings.CutPrefix(k, rootDir)
|
|
|
|
k = AbsPath(k)
|
|
|
|
fs.filesCache[k] = f
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fs.filesCache, nil
|
|
|
|
}
|
2024-01-07 17:09:56 +00:00
|
|
|
|
2024-03-17 21:00:34 +00:00
|
|
|
func anyPeerHasFiles(file *torrent.File) bool {
|
|
|
|
for _, conn := range file.Torrent().PeerConns() {
|
|
|
|
if bitmapHaveFile(conn.PeerPieces(), file) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func bitmapHaveFile(bitmap *roaring.Bitmap, file *torrent.File) bool {
|
|
|
|
for i := file.BeginPieceIndex(); i < file.EndPieceIndex(); i++ {
|
|
|
|
if !bitmap.ContainsInt(i) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
func listFilesRecursive(vfs Filesystem, start string) (map[string]File, error) {
|
|
|
|
out := make(map[string]File, 0)
|
|
|
|
entries, err := vfs.ReadDir(start)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
|
|
filename := path.Join(start, entry.Name())
|
|
|
|
if entry.IsDir() {
|
|
|
|
rec, err := listFilesRecursive(vfs, filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2024-01-07 17:09:56 +00:00
|
|
|
}
|
2024-01-28 20:22:49 +00:00
|
|
|
maps.Copy(out, rec)
|
|
|
|
} else {
|
|
|
|
file, err := vfs.Open(filename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2024-01-07 17:09:56 +00:00
|
|
|
}
|
2024-01-28 20:22:49 +00:00
|
|
|
out[filename] = file
|
2024-01-07 17:09:56 +00:00
|
|
|
}
|
2023-10-16 09:18:40 +00:00
|
|
|
}
|
2021-11-16 12:13:58 +00:00
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
return out, nil
|
2023-10-16 09:18:40 +00:00
|
|
|
}
|
2021-11-16 12:13:58 +00:00
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
func (fs *TorrentFs) rawOpen(path string) (File, error) {
|
2023-12-25 22:11:03 +00:00
|
|
|
files, err := fs.files()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
file, err := getFile(files, path)
|
2023-10-16 09:18:40 +00:00
|
|
|
return file, err
|
2021-11-16 12:13:58 +00:00
|
|
|
}
|
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
func (fs *TorrentFs) rawStat(filename string) (fs.FileInfo, error) {
|
2023-12-25 22:11:03 +00:00
|
|
|
files, err := fs.files()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
file, err := getFile(files, filename)
|
2023-12-21 23:15:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-01-28 20:22:49 +00:00
|
|
|
return file.Stat()
|
2023-12-21 23:15:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stat implements Filesystem.
|
|
|
|
func (fs *TorrentFs) Stat(filename string) (fs.FileInfo, error) {
|
|
|
|
if filename == Separator {
|
|
|
|
return newDirInfo(filename), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
fsPath, nestedFs, nestedFsPath, err := fs.resolver.resolvePath(filename, fs.rawOpen)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if nestedFs != nil {
|
|
|
|
return nestedFs.Stat(nestedFsPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fs.rawStat(fsPath)
|
|
|
|
}
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
func (fs *TorrentFs) Open(filename string) (File, error) {
|
|
|
|
fsPath, nestedFs, nestedFsPath, err := fs.resolver.resolvePath(filename, fs.rawOpen)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-11-16 12:13:58 +00:00
|
|
|
}
|
2023-10-16 09:18:40 +00:00
|
|
|
if nestedFs != nil {
|
|
|
|
return nestedFs.Open(nestedFsPath)
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
2021-03-06 22:08:15 +00:00
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
return fs.rawOpen(fsPath)
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
func (fs *TorrentFs) ReadDir(name string) ([]fs.DirEntry, error) {
|
2023-10-16 09:18:40 +00:00
|
|
|
fsPath, nestedFs, nestedFsPath, err := fs.resolver.resolvePath(name, fs.rawOpen)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if nestedFs != nil {
|
|
|
|
return nestedFs.ReadDir(nestedFsPath)
|
|
|
|
}
|
2023-12-25 22:11:03 +00:00
|
|
|
files, err := fs.files()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-09-27 19:23:47 +00:00
|
|
|
|
2023-12-25 22:11:03 +00:00
|
|
|
return listDirFromFiles(files, fsPath)
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
|
|
|
|
2023-10-18 09:52:48 +00:00
|
|
|
func (fs *TorrentFs) Unlink(name string) error {
|
2023-12-31 22:54:55 +00:00
|
|
|
name = AbsPath(name)
|
|
|
|
|
2023-12-25 22:11:03 +00:00
|
|
|
fs.mu.Lock()
|
|
|
|
defer fs.mu.Unlock()
|
|
|
|
|
|
|
|
files, err := fs.files()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-12-31 22:54:55 +00:00
|
|
|
if !slices.Contains(maps.Keys(files), name) {
|
2023-12-25 22:11:03 +00:00
|
|
|
return ErrNotExist
|
|
|
|
}
|
|
|
|
|
2023-12-31 22:54:55 +00:00
|
|
|
file := files[name]
|
|
|
|
delete(fs.filesCache, name)
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
tfile, ok := file.(*torrentFile)
|
|
|
|
if !ok {
|
|
|
|
return ErrNotImplemented
|
|
|
|
}
|
|
|
|
|
2024-03-19 21:30:37 +00:00
|
|
|
return fs.Torrent.ExcludeFile(context.Background(), tfile.file)
|
2023-10-18 09:52:48 +00:00
|
|
|
}
|
|
|
|
|
2021-12-01 18:59:21 +00:00
|
|
|
type reader interface {
|
|
|
|
iio.Reader
|
|
|
|
missinggo.ReadContexter
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
|
|
|
|
2021-12-01 18:59:21 +00:00
|
|
|
type readAtWrapper struct {
|
|
|
|
timeout int
|
|
|
|
mu sync.Mutex
|
2020-09-27 19:23:47 +00:00
|
|
|
|
2021-12-01 18:59:21 +00:00
|
|
|
torrent.Reader
|
|
|
|
io.ReaderAt
|
|
|
|
io.Closer
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
|
|
|
|
2021-12-01 18:59:21 +00:00
|
|
|
func newReadAtWrapper(r torrent.Reader, timeout int) reader {
|
2023-10-16 09:18:40 +00:00
|
|
|
w := &readAtWrapper{Reader: r, timeout: timeout}
|
|
|
|
w.SetResponsive()
|
|
|
|
return w
|
2021-11-29 10:07:54 +00:00
|
|
|
}
|
|
|
|
|
2021-12-01 18:59:21 +00:00
|
|
|
func (rw *readAtWrapper) ReadAt(p []byte, off int64) (int, error) {
|
|
|
|
rw.mu.Lock()
|
|
|
|
defer rw.mu.Unlock()
|
|
|
|
_, err := rw.Seek(off, io.SeekStart)
|
2021-11-29 10:07:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2021-12-01 18:59:21 +00:00
|
|
|
|
|
|
|
return readAtLeast(rw, rw.timeout, p, len(p))
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
|
|
|
|
2021-12-01 18:59:21 +00:00
|
|
|
func readAtLeast(r missinggo.ReadContexter, timeout int, buf []byte, min int) (n int, err error) {
|
2021-11-29 10:07:54 +00:00
|
|
|
if len(buf) < min {
|
|
|
|
return 0, io.ErrShortBuffer
|
|
|
|
}
|
|
|
|
for n < min && err == nil {
|
|
|
|
var nn int
|
|
|
|
|
2023-10-18 09:52:48 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
|
|
|
|
defer cancel()
|
2021-11-29 10:07:54 +00:00
|
|
|
|
2021-12-01 18:59:21 +00:00
|
|
|
nn, err = r.ReadContext(ctx, buf[n:])
|
2021-11-29 10:07:54 +00:00
|
|
|
n += nn
|
|
|
|
}
|
|
|
|
if n >= min {
|
|
|
|
err = nil
|
|
|
|
} else if n > 0 && err == io.EOF {
|
|
|
|
err = io.ErrUnexpectedEOF
|
|
|
|
}
|
|
|
|
return
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
2021-12-01 18:59:21 +00:00
|
|
|
|
|
|
|
func (rw *readAtWrapper) Close() error {
|
|
|
|
rw.mu.Lock()
|
|
|
|
defer rw.mu.Unlock()
|
|
|
|
return rw.Reader.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ File = &torrentFile{}
|
|
|
|
|
|
|
|
type torrentFile struct {
|
2023-12-21 23:15:39 +00:00
|
|
|
name string
|
|
|
|
|
2023-12-31 22:54:55 +00:00
|
|
|
reader reader
|
|
|
|
timeout int
|
|
|
|
|
|
|
|
file *torrent.File
|
2021-12-01 18:59:21 +00:00
|
|
|
}
|
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
func (d *torrentFile) Stat() (fs.FileInfo, error) {
|
2023-12-31 22:54:55 +00:00
|
|
|
return newFileInfo(d.name, d.file.Length()), nil
|
2023-12-21 23:15:39 +00:00
|
|
|
}
|
|
|
|
|
2021-12-01 18:59:21 +00:00
|
|
|
func (d *torrentFile) load() {
|
|
|
|
if d.reader != nil {
|
|
|
|
return
|
|
|
|
}
|
2023-12-31 22:54:55 +00:00
|
|
|
d.reader = newReadAtWrapper(d.file.NewReader(), d.timeout)
|
2021-12-01 18:59:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *torrentFile) Size() int64 {
|
2023-12-31 22:54:55 +00:00
|
|
|
return d.file.Length()
|
2021-12-01 18:59:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *torrentFile) IsDir() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *torrentFile) Close() error {
|
|
|
|
var err error
|
|
|
|
if d.reader != nil {
|
|
|
|
err = d.reader.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
d.reader = nil
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *torrentFile) Read(p []byte) (n int, err error) {
|
|
|
|
d.load()
|
2023-10-18 09:52:48 +00:00
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(d.timeout)*time.Second)
|
|
|
|
defer cancel()
|
2021-12-01 18:59:21 +00:00
|
|
|
|
|
|
|
return d.reader.ReadContext(ctx, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *torrentFile) ReadAt(p []byte, off int64) (n int, err error) {
|
|
|
|
d.load()
|
|
|
|
return d.reader.ReadAt(p, off)
|
|
|
|
}
|