[feature] file exclude

This commit is contained in:
royalcat 2023-12-26 01:11:03 +03:00
parent 0350ecba9a
commit 0332206560
13 changed files with 243 additions and 19 deletions

View file

@ -0,0 +1,90 @@
package repository
import (
"errors"
"sync"
"github.com/anacrolix/torrent/metainfo"
"github.com/philippgille/gokv"
"github.com/philippgille/gokv/badgerdb"
"github.com/philippgille/gokv/encoding"
)
type TorrentMetaRepository interface {
ExcludeFile(hash metainfo.Hash, file ...string) error
ExcludedFiles(hash metainfo.Hash) ([]string, error)
}
func NewTorrentMetaRepository(dir string) (TorrentMetaRepository, error) {
store, err := badgerdb.NewStore(badgerdb.Options{
Dir: dir,
Codec: encoding.JSON,
})
if err != nil {
return nil, err
}
r := &torrentRepositoryImpl{
store: store,
}
return r, nil
}
type torrentRepositoryImpl struct {
m sync.RWMutex
store gokv.Store
}
type torrentMeta struct {
ExludedFiles []string
}
var ErrNotFound = errors.New("not found")
func (r *torrentRepositoryImpl) ExcludeFile(hash metainfo.Hash, file ...string) error {
r.m.Lock()
defer r.m.Unlock()
var meta torrentMeta
found, err := r.store.Get(hash.AsString(), &meta)
if err != nil {
return err
}
if !found {
meta = torrentMeta{
ExludedFiles: file,
}
}
meta.ExludedFiles = unique(append(meta.ExludedFiles, file...))
return r.store.Set(hash.AsString(), meta)
}
func (r *torrentRepositoryImpl) ExcludedFiles(hash metainfo.Hash) ([]string, error) {
r.m.Lock()
defer r.m.Unlock()
var meta torrentMeta
found, err := r.store.Get(hash.AsString(), &meta)
if err != nil {
return nil, err
}
if !found {
return nil, nil
}
return meta.ExludedFiles, nil
}
func unique[C comparable](intSlice []C) []C {
keys := make(map[C]bool)
list := []C{}
for _, entry := range intSlice {
if _, value := keys[entry]; !value {
keys[entry] = true
list = append(list, entry)
}
}
return list
}

View file

@ -6,6 +6,7 @@ import (
"log/slog"
"time"
"git.kmsign.ru/royalcat/tstor/src/host/repository"
"git.kmsign.ru/royalcat/tstor/src/host/vfs"
"github.com/anacrolix/torrent"
"github.com/anacrolix/torrent/metainfo"
@ -13,7 +14,8 @@ import (
)
type Service struct {
c *torrent.Client
c *torrent.Client
rep repository.TorrentMetaRepository
// stats *Stats
DefaultPriority types.PiecePriority
@ -22,12 +24,13 @@ type Service struct {
addTimeout, readTimeout int
}
func NewService(c *torrent.Client, addTimeout, readTimeout int) *Service {
func NewService(c *torrent.Client, rep repository.TorrentMetaRepository, addTimeout, readTimeout int) *Service {
l := slog.With("component", "torrent-service")
return &Service{
log: l,
c: c,
DefaultPriority: types.PiecePriorityNone,
rep: rep,
// stats: newStats(), // TODO persistent
addTimeout: addTimeout,
readTimeout: readTimeout,
@ -63,7 +66,7 @@ func (s *Service) NewTorrentFs(f vfs.File) (vfs.Filesystem, error) {
t.AllowDataDownload()
}
return vfs.NewTorrentFs(t, s.readTimeout), nil
return vfs.NewTorrentFs(t, s.rep, s.readTimeout), nil
}
func (s *Service) Stats() (*Stats, error) {

View file

@ -49,7 +49,10 @@ func NewArchive(r iio.Reader, size int64, loader ArchiveLoader) *archive {
}
}
var _ Filesystem = &archive{}
// Unlink implements Filesystem.
func (a *archive) Unlink(filename string) error {
return ErrNotImplemented
}
func (a *archive) Open(filename string) (File, error) {
files, err := a.files()

View file

@ -30,6 +30,7 @@ type Filesystem interface {
ReadDir(path string) ([]fs.DirEntry, error)
Stat(filename string) (fs.FileInfo, error)
Unlink(filename string) error
}
const defaultMode = fs.FileMode(0555)

View file

@ -12,6 +12,11 @@ type MemoryFs struct {
files map[string]*MemoryFile
}
// Unlink implements Filesystem.
func (fs *MemoryFs) Unlink(filename string) error {
return ErrNotImplemented
}
func NewMemoryFS(files map[string]*MemoryFile) *MemoryFs {
return &MemoryFs{
files: files,

View file

@ -11,6 +11,11 @@ type OsFS struct {
hostDir string
}
// Unlink implements Filesystem.
func (fs *OsFS) Unlink(filename string) error {
return fs.Unlink(filename)
}
// Stat implements Filesystem.
func (fs *OsFS) Stat(filename string) (fs.FileInfo, error) {
if path.Clean(filename) == Separator {

View file

@ -72,6 +72,19 @@ func (r *ResolveFS) Stat(filename string) (fs.FileInfo, error) {
return r.rootFS.Stat(fsPath)
}
// Unlink implements Filesystem.
func (r *ResolveFS) Unlink(filename string) error {
fsPath, nestedFs, nestedFsPath, err := r.resolver.resolvePath(filename, r.rootFS.Open)
if err != nil {
return err
}
if nestedFs != nil {
return nestedFs.Unlink(nestedFsPath)
}
return r.rootFS.Unlink(fsPath)
}
var _ Filesystem = &ResolveFS{}
type FsFactory func(f File) (Filesystem, error)

View file

@ -52,6 +52,10 @@ func (d *DummyFs) Open(filename string) (File, error) {
return &Dummy{}, nil
}
func (d *DummyFs) Unlink(filename string) error {
return ErrNotImplemented
}
func (d *DummyFs) ReadDir(path string) ([]fs.DirEntry, error) {
if path == "/dir/here" {
return []fs.DirEntry{

View file

@ -5,19 +5,23 @@ import (
"io"
"io/fs"
"path"
"slices"
"sync"
"time"
"git.kmsign.ru/royalcat/tstor/src/host/repository"
"git.kmsign.ru/royalcat/tstor/src/iio"
"github.com/anacrolix/missinggo/v2"
"github.com/anacrolix/torrent"
"golang.org/x/exp/maps"
)
var _ Filesystem = &TorrentFs{}
type TorrentFs struct {
mu sync.Mutex
t *torrent.Torrent
mu sync.Mutex
t *torrent.Torrent
rep repository.TorrentMetaRepository
readTimeout int
@ -27,22 +31,34 @@ type TorrentFs struct {
resolver *resolver
}
func NewTorrentFs(t *torrent.Torrent, readTimeout int) *TorrentFs {
func NewTorrentFs(t *torrent.Torrent, rep repository.TorrentMetaRepository, readTimeout int) *TorrentFs {
return &TorrentFs{
t: t,
rep: rep,
readTimeout: readTimeout,
resolver: newResolver(ArchiveFactories),
}
}
func (fs *TorrentFs) files() map[string]*torrentFile {
func (fs *TorrentFs) files() (map[string]*torrentFile, error) {
if fs.filesCache == nil {
fs.mu.Lock()
<-fs.t.GotInfo()
files := fs.t.Files()
excludedFiles, err := fs.rep.ExcludedFiles(fs.t.InfoHash())
if err != nil {
return nil, err
}
fs.filesCache = make(map[string]*torrentFile)
for _, file := range files {
p := AbsPath(file.Path())
if slices.Contains(excludedFiles, p) {
continue
}
fs.filesCache[p] = &torrentFile{
name: path.Base(p),
readerFunc: file.NewReader,
@ -53,16 +69,24 @@ func (fs *TorrentFs) files() map[string]*torrentFile {
fs.mu.Unlock()
}
return fs.filesCache
return fs.filesCache, nil
}
func (fs *TorrentFs) rawOpen(path string) (File, error) {
file, err := getFile(fs.files(), path)
files, err := fs.files()
if err != nil {
return nil, err
}
file, err := getFile(files, path)
return file, err
}
func (fs *TorrentFs) rawStat(filename string) (fs.FileInfo, error) {
file, err := getFile(fs.files(), filename)
files, err := fs.files()
if err != nil {
return nil, err
}
file, err := getFile(files, filename)
if err != nil {
return nil, err
}
@ -111,14 +135,30 @@ func (fs *TorrentFs) ReadDir(name string) ([]fs.DirEntry, error) {
if nestedFs != nil {
return nestedFs.ReadDir(nestedFsPath)
}
files, err := fs.files()
if err != nil {
return nil, err
}
return listDirFromFiles(fs.files(), fsPath)
return listDirFromFiles(files, fsPath)
}
func (fs *TorrentFs) Unlink(name string) error {
file := fs.t.Files()[0]
file.SetPriority(torrent.PiecePriorityNone)
return nil
fs.mu.Lock()
defer fs.mu.Unlock()
files, err := fs.files()
if err != nil {
return err
}
file := AbsPath(name)
if !slices.Contains(maps.Keys(files), file) {
return ErrNotExist
}
fs.filesCache = nil
return fs.rep.ExcludeFile(fs.t.InfoHash(), file)
}
type reader interface {

View file

@ -48,7 +48,7 @@ func (wd *WebDAV) Mkdir(ctx context.Context, name string, perm fs.FileMode) erro
}
func (wd *WebDAV) RemoveAll(ctx context.Context, name string) error {
return webdav.ErrNotImplemented
return wd.fs.Unlink(name)
}
func (wd *WebDAV) Rename(ctx context.Context, oldName, newName string) error {