[feature] file exclude
This commit is contained in:
parent
0350ecba9a
commit
0332206560
13 changed files with 243 additions and 19 deletions
src
host
repository
torrent
vfs
mounts/webdav
90
src/host/repository/repository.go
Normal file
90
src/host/repository/repository.go
Normal 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
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue