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
|
|
|
|
2023-12-31 22:54:55 +00:00
|
|
|
"git.kmsign.ru/royalcat/tstor/src/host/storage"
|
2023-10-08 16:46:03 +00:00
|
|
|
"git.kmsign.ru/royalcat/tstor/src/iio"
|
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 {
|
2023-12-25 22:11:03 +00:00
|
|
|
mu sync.Mutex
|
|
|
|
t *torrent.Torrent
|
2023-12-31 22:54:55 +00:00
|
|
|
rep storage.TorrentsRepository
|
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
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
//cache
|
|
|
|
filesCache map[string]*torrentFile
|
|
|
|
|
2023-10-16 09:18:40 +00:00
|
|
|
resolver *resolver
|
2020-09-27 19:23:47 +00:00
|
|
|
}
|
|
|
|
|
2023-12-31 22:54:55 +00:00
|
|
|
func NewTorrentFs(t *torrent.Torrent, rep storage.TorrentsRepository, readTimeout int) *TorrentFs {
|
2023-10-16 09:18:40 +00:00
|
|
|
return &TorrentFs{
|
|
|
|
t: t,
|
2023-12-25 22:11:03 +00:00
|
|
|
rep: rep,
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-25 22:11:03 +00:00
|
|
|
func (fs *TorrentFs) files() (map[string]*torrentFile, error) {
|
2023-12-21 23:15:39 +00:00
|
|
|
if fs.filesCache == nil {
|
|
|
|
fs.mu.Lock()
|
|
|
|
<-fs.t.GotInfo()
|
|
|
|
files := fs.t.Files()
|
2023-12-25 22:11:03 +00:00
|
|
|
|
|
|
|
excludedFiles, err := fs.rep.ExcludedFiles(fs.t.InfoHash())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
fs.filesCache = make(map[string]*torrentFile)
|
|
|
|
for _, file := range files {
|
2023-12-25 22:11:03 +00:00
|
|
|
|
2024-01-01 18:17:32 +00:00
|
|
|
p := file.Path()
|
|
|
|
|
|
|
|
if slices.Contains(excludedFiles, p) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if strings.Contains(p, "/.pad/") {
|
2023-12-25 22:11:03 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2024-01-01 18:17:32 +00:00
|
|
|
p = AbsPath(file.Path())
|
|
|
|
|
|
|
|
// TODO make optional
|
|
|
|
// removing the torrent root directory of same name as torrent
|
|
|
|
p, _ = strings.CutPrefix(p, "/"+fs.t.Name()+"/")
|
|
|
|
p = AbsPath(p)
|
2023-12-31 22:54:55 +00:00
|
|
|
|
2023-12-21 23:15:39 +00:00
|
|
|
fs.filesCache[p] = &torrentFile{
|
2023-12-31 22:54:55 +00:00
|
|
|
name: path.Base(p),
|
|
|
|
timeout: fs.readTimeout,
|
|
|
|
file: file,
|
2023-12-21 23:15:39 +00:00
|
|
|
}
|
2023-10-16 09:18:40 +00:00
|
|
|
}
|
2023-12-21 23:15:39 +00:00
|
|
|
fs.mu.Unlock()
|
2023-10-16 09:18:40 +00:00
|
|
|
}
|
2021-11-16 12:13:58 +00:00
|
|
|
|
2023-12-25 22:11:03 +00:00
|
|
|
return fs.filesCache, 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
|
|
|
|
}
|
|
|
|
if file.IsDir() {
|
|
|
|
return newDirInfo(path.Base(filename)), nil
|
|
|
|
} else {
|
|
|
|
return newFileInfo(path.Base(filename), file.Size()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
|
|
return fs.rep.ExcludeFile(file.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)
|
|
|
|
}
|