2014-03-17 14:44:22 +00:00
|
|
|
package torrentfs
|
|
|
|
|
|
|
|
import (
|
2014-08-27 22:08:09 +00:00
|
|
|
"expvar"
|
2014-09-13 17:44:07 +00:00
|
|
|
"fmt"
|
2016-01-18 09:12:51 +00:00
|
|
|
"io"
|
2014-08-21 11:08:56 +00:00
|
|
|
"os"
|
2015-08-23 09:25:33 +00:00
|
|
|
"path"
|
2014-08-21 11:08:56 +00:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
2015-03-26 06:18:08 +00:00
|
|
|
"bazil.org/fuse"
|
|
|
|
fusefs "bazil.org/fuse/fs"
|
2015-02-06 05:03:33 +00:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
|
2015-03-20 05:37:44 +00:00
|
|
|
"github.com/anacrolix/torrent"
|
2015-04-28 05:24:17 +00:00
|
|
|
"github.com/anacrolix/torrent/metainfo"
|
2014-03-17 14:44:22 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
defaultMode = 0555
|
|
|
|
)
|
|
|
|
|
2014-08-27 22:08:09 +00:00
|
|
|
var (
|
|
|
|
torrentfsReadRequests = expvar.NewInt("torrentfsReadRequests")
|
|
|
|
torrentfsDelayedReadRequests = expvar.NewInt("torrentfsDelayedReadRequests")
|
|
|
|
interruptedReads = expvar.NewInt("interruptedReads")
|
|
|
|
)
|
|
|
|
|
2014-12-01 20:30:50 +00:00
|
|
|
type TorrentFS struct {
|
2014-12-03 18:53:10 +00:00
|
|
|
Client *torrent.Client
|
|
|
|
destroyed chan struct{}
|
|
|
|
mu sync.Mutex
|
|
|
|
blockedReads int
|
|
|
|
event sync.Cond
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
|
2014-08-27 22:08:59 +00:00
|
|
|
var (
|
2014-12-01 20:30:50 +00:00
|
|
|
_ fusefs.FSDestroyer = &TorrentFS{}
|
2014-08-27 22:08:59 +00:00
|
|
|
|
2015-04-01 01:15:44 +00:00
|
|
|
_ fusefs.NodeForgetter = rootNode{}
|
|
|
|
_ fusefs.HandleReadDirAller = rootNode{}
|
|
|
|
_ fusefs.HandleReadDirAller = dirNode{}
|
|
|
|
)
|
2014-03-18 11:39:33 +00:00
|
|
|
|
2014-03-17 14:44:22 +00:00
|
|
|
type rootNode struct {
|
2014-12-01 20:30:50 +00:00
|
|
|
fs *TorrentFS
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type node struct {
|
2015-08-23 09:25:33 +00:00
|
|
|
path string
|
2015-02-26 11:17:58 +00:00
|
|
|
metadata *metainfo.Info
|
2014-12-01 20:30:50 +00:00
|
|
|
FS *TorrentFS
|
2016-01-05 08:42:26 +00:00
|
|
|
t torrent.Torrent
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type fileNode struct {
|
|
|
|
node
|
|
|
|
size uint64
|
|
|
|
TorrentOffset int64
|
|
|
|
}
|
|
|
|
|
2015-06-02 13:59:25 +00:00
|
|
|
func (fn fileNode) Attr(ctx context.Context, attr *fuse.Attr) error {
|
2014-03-17 14:44:22 +00:00
|
|
|
attr.Size = fn.size
|
|
|
|
attr.Mode = defaultMode
|
2015-06-02 13:59:25 +00:00
|
|
|
return nil
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
|
2014-08-21 15:37:34 +00:00
|
|
|
func (n *node) fsPath() string {
|
2015-08-23 09:25:33 +00:00
|
|
|
return "/" + n.metadata.Name + "/" + n.path
|
2014-08-21 15:37:34 +00:00
|
|
|
}
|
|
|
|
|
2016-01-05 08:42:26 +00:00
|
|
|
func blockingRead(ctx context.Context, fs *TorrentFS, t torrent.Torrent, off int64, p []byte) (n int, err error) {
|
2014-12-03 18:53:10 +00:00
|
|
|
fs.mu.Lock()
|
|
|
|
fs.blockedReads++
|
|
|
|
fs.event.Broadcast()
|
|
|
|
fs.mu.Unlock()
|
2014-12-03 07:07:50 +00:00
|
|
|
var (
|
|
|
|
_n int
|
2015-02-06 05:03:33 +00:00
|
|
|
_err error
|
2014-12-03 07:07:50 +00:00
|
|
|
)
|
|
|
|
readDone := make(chan struct{})
|
|
|
|
go func() {
|
2016-02-21 15:44:29 +00:00
|
|
|
defer close(readDone)
|
2015-04-14 13:59:41 +00:00
|
|
|
r := t.NewReader()
|
|
|
|
defer r.Close()
|
2016-01-18 09:12:51 +00:00
|
|
|
_, _err = r.Seek(off, os.SEEK_SET)
|
|
|
|
if _err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
_n, _err = io.ReadFull(r, p)
|
2014-12-03 07:07:50 +00:00
|
|
|
}()
|
2014-09-13 17:44:07 +00:00
|
|
|
select {
|
2014-12-03 07:07:50 +00:00
|
|
|
case <-readDone:
|
|
|
|
n = _n
|
|
|
|
err = _err
|
2014-09-13 17:44:07 +00:00
|
|
|
case <-fs.destroyed:
|
|
|
|
err = fuse.EIO
|
2015-02-06 05:03:33 +00:00
|
|
|
case <-ctx.Done():
|
2014-09-13 17:44:07 +00:00
|
|
|
err = fuse.EINTR
|
|
|
|
}
|
2014-12-03 18:53:10 +00:00
|
|
|
fs.mu.Lock()
|
|
|
|
fs.blockedReads--
|
|
|
|
fs.event.Broadcast()
|
|
|
|
fs.mu.Unlock()
|
2014-09-13 17:44:07 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-01-05 08:42:26 +00:00
|
|
|
func readFull(ctx context.Context, fs *TorrentFS, t torrent.Torrent, off int64, p []byte) (n int, err error) {
|
2014-09-13 17:44:07 +00:00
|
|
|
for len(p) != 0 {
|
|
|
|
var nn int
|
2015-02-06 05:03:33 +00:00
|
|
|
nn, err = blockingRead(ctx, fs, t, off, p)
|
2014-09-13 17:44:07 +00:00
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
n += nn
|
|
|
|
off += int64(nn)
|
|
|
|
p = p[nn:]
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-02-06 05:03:33 +00:00
|
|
|
func (fn fileNode) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
|
2014-08-27 22:08:09 +00:00
|
|
|
torrentfsReadRequests.Add(1)
|
2014-03-17 14:44:22 +00:00
|
|
|
if req.Dir {
|
2014-08-25 12:14:10 +00:00
|
|
|
panic("read on directory")
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
2014-05-22 14:34:18 +00:00
|
|
|
size := req.Size
|
2014-08-25 12:15:18 +00:00
|
|
|
fileLeft := int64(fn.size) - req.Offset
|
2014-08-27 22:09:41 +00:00
|
|
|
if fileLeft < 0 {
|
|
|
|
fileLeft = 0
|
|
|
|
}
|
2014-08-25 12:15:18 +00:00
|
|
|
if fileLeft < int64(size) {
|
|
|
|
size = int(fileLeft)
|
2014-05-22 14:34:18 +00:00
|
|
|
}
|
2014-08-25 12:15:18 +00:00
|
|
|
resp.Data = resp.Data[:size]
|
|
|
|
if len(resp.Data) == 0 {
|
|
|
|
return nil
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
torrentOff := fn.TorrentOffset + req.Offset
|
2015-02-06 05:03:33 +00:00
|
|
|
n, err := readFull(ctx, fn.FS, fn.t, torrentOff, resp.Data)
|
2014-09-13 17:44:07 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if n != size {
|
|
|
|
panic(fmt.Sprintf("%d < %d", n, size))
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
2014-09-13 17:44:07 +00:00
|
|
|
return nil
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type dirNode struct {
|
|
|
|
node
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
2015-02-06 05:03:33 +00:00
|
|
|
_ fusefs.HandleReadDirAller = dirNode{}
|
|
|
|
_ fusefs.HandleReader = fileNode{}
|
2014-03-17 14:44:22 +00:00
|
|
|
)
|
|
|
|
|
2015-08-23 09:25:33 +00:00
|
|
|
func isSubPath(parent, child string) bool {
|
|
|
|
if !strings.HasPrefix(child, parent) {
|
2014-03-17 14:44:22 +00:00
|
|
|
return false
|
|
|
|
}
|
2015-08-23 09:25:33 +00:00
|
|
|
s := child[len(parent):]
|
|
|
|
if len(s) == 0 {
|
|
|
|
return false
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
2015-08-23 09:25:33 +00:00
|
|
|
return s[0] == '/'
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
|
2015-02-06 05:03:33 +00:00
|
|
|
func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
|
2014-03-17 14:44:22 +00:00
|
|
|
names := map[string]bool{}
|
2014-06-28 09:38:31 +00:00
|
|
|
for _, fi := range dn.metadata.Files {
|
2015-08-23 09:25:33 +00:00
|
|
|
if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
|
2014-03-17 14:44:22 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
name := fi.Path[len(dn.path)]
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-02-06 05:03:33 +00:00
|
|
|
func (dn dirNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
|
2014-03-17 14:44:22 +00:00
|
|
|
var torrentOffset int64
|
2014-06-28 09:38:31 +00:00
|
|
|
for _, fi := range dn.metadata.Files {
|
2015-08-23 09:25:33 +00:00
|
|
|
if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
|
2014-03-17 14:44:22 +00:00
|
|
|
torrentOffset += fi.Length
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if fi.Path[len(dn.path)] != name {
|
|
|
|
torrentOffset += fi.Length
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
__node := dn.node
|
2015-08-23 09:25:33 +00:00
|
|
|
__node.path = path.Join(__node.path, name)
|
2014-03-17 14:44:22 +00:00
|
|
|
if len(fi.Path) == len(dn.path)+1 {
|
|
|
|
_node = fileNode{
|
|
|
|
node: __node,
|
|
|
|
size: uint64(fi.Length),
|
|
|
|
TorrentOffset: torrentOffset,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_node = dirNode{__node}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if _node == nil {
|
|
|
|
err = fuse.ENOENT
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-06-02 13:59:25 +00:00
|
|
|
func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
|
2014-03-17 14:44:22 +00:00
|
|
|
attr.Mode = os.ModeDir | defaultMode
|
2015-06-02 13:59:25 +00:00
|
|
|
return nil
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
|
2015-02-06 05:03:33 +00:00
|
|
|
func (me rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
|
2014-06-28 09:38:31 +00:00
|
|
|
for _, t := range me.fs.Client.Torrents() {
|
2015-04-28 05:24:17 +00:00
|
|
|
info := t.Info()
|
2016-01-06 01:19:49 +00:00
|
|
|
if t.Name() != name || info == nil {
|
2014-06-28 09:38:31 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
__node := node{
|
2015-04-28 05:24:17 +00:00
|
|
|
metadata: info,
|
2014-06-28 09:38:31 +00:00
|
|
|
FS: me.fs,
|
2014-12-03 07:07:50 +00:00
|
|
|
t: t,
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
2015-04-28 05:24:17 +00:00
|
|
|
if !info.IsDir() {
|
|
|
|
_node = fileNode{__node, uint64(info.Length), 0}
|
2014-06-28 09:38:31 +00:00
|
|
|
} else {
|
|
|
|
_node = dirNode{__node}
|
|
|
|
}
|
|
|
|
break
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
if _node == nil {
|
|
|
|
err = fuse.ENOENT
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-04-01 01:15:44 +00:00
|
|
|
func (me rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
|
2014-11-18 20:36:27 +00:00
|
|
|
for _, t := range me.fs.Client.Torrents() {
|
2015-04-28 05:24:17 +00:00
|
|
|
info := t.Info()
|
|
|
|
if info == nil {
|
2014-07-23 04:55:38 +00:00
|
|
|
continue
|
|
|
|
}
|
2014-03-17 14:44:22 +00:00
|
|
|
dirents = append(dirents, fuse.Dirent{
|
2015-04-28 05:24:17 +00:00
|
|
|
Name: info.Name,
|
2014-03-17 14:44:22 +00:00
|
|
|
Type: func() fuse.DirentType {
|
2015-04-28 05:24:17 +00:00
|
|
|
if !info.IsDir() {
|
2014-03-17 14:44:22 +00:00
|
|
|
return fuse.DT_File
|
|
|
|
} else {
|
|
|
|
return fuse.DT_Dir
|
|
|
|
}
|
|
|
|
}(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-06-02 13:59:25 +00:00
|
|
|
func (rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
|
2015-03-25 06:25:24 +00:00
|
|
|
attr.Mode = os.ModeDir
|
2015-06-02 13:59:25 +00:00
|
|
|
return nil
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
|
|
|
|
2014-04-08 15:15:39 +00:00
|
|
|
// TODO(anacrolix): Why should rootNode implement this?
|
2014-04-17 06:37:54 +00:00
|
|
|
func (me rootNode) Forget() {
|
|
|
|
me.fs.Destroy()
|
2014-03-18 11:39:33 +00:00
|
|
|
}
|
|
|
|
|
2015-02-06 05:03:33 +00:00
|
|
|
func (tfs *TorrentFS) Root() (fusefs.Node, error) {
|
2014-03-17 14:44:22 +00:00
|
|
|
return rootNode{tfs}, nil
|
|
|
|
}
|
|
|
|
|
2014-12-01 20:30:50 +00:00
|
|
|
func (me *TorrentFS) Destroy() {
|
2014-04-17 06:37:54 +00:00
|
|
|
me.mu.Lock()
|
|
|
|
select {
|
|
|
|
case <-me.destroyed:
|
|
|
|
default:
|
|
|
|
close(me.destroyed)
|
|
|
|
}
|
|
|
|
me.mu.Unlock()
|
|
|
|
}
|
|
|
|
|
2014-12-01 20:30:50 +00:00
|
|
|
func New(cl *torrent.Client) *TorrentFS {
|
|
|
|
fs := &TorrentFS{
|
2014-04-17 06:37:54 +00:00
|
|
|
Client: cl,
|
|
|
|
destroyed: make(chan struct{}),
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|
2014-12-03 18:53:10 +00:00
|
|
|
fs.event.L = &fs.mu
|
2014-03-18 11:39:33 +00:00
|
|
|
return fs
|
2014-03-17 14:44:22 +00:00
|
|
|
}
|