FedP2P/fs/torrentfs.go

220 lines
3.9 KiB
Go
Raw Normal View History

package torrentfs
import (
2018-01-31 13:42:26 +08:00
"context"
"expvar"
2014-08-21 19:08:56 +08:00
"os"
"strings"
"sync"
"bazil.org/fuse"
fusefs "bazil.org/fuse/fs"
2019-08-21 18:58:40 +08:00
"github.com/anacrolix/torrent"
2015-04-28 13:24:17 +08:00
"github.com/anacrolix/torrent/metainfo"
)
const (
defaultMode = 0555
)
var (
2020-02-20 08:09:57 +08:00
torrentfsReadRequests = expvar.NewInt("torrentfsReadRequests")
)
type TorrentFS struct {
2014-12-04 02:53:10 +08:00
Client *torrent.Client
destroyed chan struct{}
mu sync.Mutex
blockedReads int
event sync.Cond
}
2014-08-28 06:08:59 +08:00
var (
_ fusefs.FSDestroyer = &TorrentFS{}
2014-08-28 06:08:59 +08:00
_ fusefs.NodeForgetter = rootNode{}
_ fusefs.HandleReadDirAller = rootNode{}
_ fusefs.HandleReadDirAller = dirNode{}
)
2018-01-06 13:37:40 +08:00
// Is a directory node that lists all torrents and handles destruction of the
// filesystem.
type rootNode struct {
fs *TorrentFS
}
type node struct {
path string
metadata *metainfo.Info
FS *TorrentFS
2016-04-03 16:40:43 +08:00
t *torrent.Torrent
}
type dirNode struct {
node
}
var (
_ fusefs.HandleReadDirAller = dirNode{}
)
func isSubPath(parent, child string) bool {
if parent == "" {
return len(child) > 0
}
if !strings.HasPrefix(child, parent) {
return false
}
2018-01-06 13:37:13 +08:00
extra := child[len(parent):]
if extra == "" {
return false
}
2018-01-06 13:37:13 +08:00
// Not just a file with more stuff on the end.
return extra[0] == '/'
}
func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
names := map[string]bool{}
2014-06-28 17:38:31 +08:00
for _, fi := range dn.metadata.Files {
filePathname := strings.Join(fi.Path, "/")
if !isSubPath(dn.path, filePathname) {
continue
}
var name string
if dn.path == "" {
name = fi.Path[0]
} else {
dirPathname := strings.Split(dn.path, "/")
name = fi.Path[len(dirPathname)]
}
if names[name] {
continue
}
names[name] = true
de := fuse.Dirent{
Name: name,
}
if len(fi.Path) == len(dn.path)+1 {
de.Type = fuse.DT_File
} else {
de.Type = fuse.DT_Dir
}
des = append(des, de)
}
return
}
2018-01-06 13:37:13 +08:00
func (dn dirNode) Lookup(_ context.Context, name string) (fusefs.Node, error) {
dir := false
var file *torrent.File
var fullPath string
if dn.path != "" {
fullPath = dn.path + "/" + name
} else {
fullPath = name
}
2018-01-06 13:37:13 +08:00
for _, f := range dn.t.Files() {
if f.DisplayPath() == fullPath {
2018-01-21 19:49:12 +08:00
file = f
}
2018-01-06 13:37:13 +08:00
if isSubPath(fullPath, f.DisplayPath()) {
dir = true
}
}
2018-01-06 13:37:13 +08:00
n := dn.node
n.path = fullPath
if dir && file != nil {
panic("both dir and file")
}
2018-01-06 13:37:13 +08:00
if file != nil {
return fileNode{n, file}, nil
}
if dir {
return dirNode{n}, nil
}
return nil, fuse.ENOENT
}
func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
attr.Mode = os.ModeDir | defaultMode
return nil
}
func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
for _, t := range rn.fs.Client.Torrents() {
2015-04-28 13:24:17 +08:00
info := t.Info()
if t.Name() != name || info == nil {
2014-06-28 17:38:31 +08:00
continue
}
__node := node{
2015-04-28 13:24:17 +08:00
metadata: info,
FS: rn.fs,
t: t,
}
2015-04-28 13:24:17 +08:00
if !info.IsDir() {
2018-01-21 19:49:12 +08:00
_node = fileNode{__node, t.Files()[0]}
2014-06-28 17:38:31 +08:00
} else {
_node = dirNode{__node}
}
break
}
if _node == nil {
err = fuse.ENOENT
}
return
}
func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
for _, t := range rn.fs.Client.Torrents() {
2015-04-28 13:24:17 +08:00
info := t.Info()
if info == nil {
continue
}
dirents = append(dirents, fuse.Dirent{
2015-04-28 13:24:17 +08:00
Name: info.Name,
Type: func() fuse.DirentType {
2015-04-28 13:24:17 +08:00
if !info.IsDir() {
return fuse.DT_File
} else {
return fuse.DT_Dir
}
}(),
})
}
return
}
func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
2015-03-25 14:25:24 +08:00
attr.Mode = os.ModeDir
return nil
}
// TODO(anacrolix): Why should rootNode implement this?
func (rn rootNode) Forget() {
rn.fs.Destroy()
}
func (tfs *TorrentFS) Root() (fusefs.Node, error) {
return rootNode{tfs}, nil
}
func (tfs *TorrentFS) Destroy() {
tfs.mu.Lock()
2014-04-17 14:37:54 +08:00
select {
case <-tfs.destroyed:
2014-04-17 14:37:54 +08:00
default:
close(tfs.destroyed)
2014-04-17 14:37:54 +08:00
}
tfs.mu.Unlock()
2014-04-17 14:37:54 +08:00
}
func New(cl *torrent.Client) *TorrentFS {
fs := &TorrentFS{
2014-04-17 14:37:54 +08:00
Client: cl,
destroyed: make(chan struct{}),
}
2014-12-04 02:53:10 +08:00
fs.event.L = &fs.mu
return fs
}