13 fusefs "bazil.org/fuse/fs"
14 "golang.org/x/net/context"
16 "github.com/anacrolix/torrent"
17 "github.com/anacrolix/torrent/metainfo"
25 torrentfsReadRequests = expvar.NewInt("torrentfsReadRequests")
26 torrentfsDelayedReadRequests = expvar.NewInt("torrentfsDelayedReadRequests")
27 interruptedReads = expvar.NewInt("interruptedReads")
30 type TorrentFS struct {
31 Client *torrent.Client
32 destroyed chan struct{}
39 _ fusefs.FSDestroyer = &TorrentFS{}
41 _ fusefs.NodeForgetter = rootNode{}
42 _ fusefs.HandleReadDirAller = rootNode{}
43 _ fusefs.HandleReadDirAller = dirNode{}
46 type rootNode struct {
52 metadata *metainfo.Info
57 type fileNode struct {
63 func (fn fileNode) Attr(ctx context.Context, attr *fuse.Attr) error {
65 attr.Mode = defaultMode
69 func (n *node) fsPath() string {
70 return "/" + n.metadata.Name + "/" + n.path
73 func blockingRead(ctx context.Context, fs *TorrentFS, t *torrent.Torrent, off int64, p []byte) (n int, err error) {
82 readDone := make(chan struct{})
87 _, _err = r.Seek(off, os.SEEK_SET)
91 _n, _err = io.ReadFull(r, p)
109 func readFull(ctx context.Context, fs *TorrentFS, t *torrent.Torrent, off int64, p []byte) (n int, err error) {
112 nn, err = blockingRead(ctx, fs, t, off, p)
123 func (fn fileNode) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
124 torrentfsReadRequests.Add(1)
126 panic("read on directory")
129 fileLeft := int64(fn.size) - req.Offset
133 if fileLeft < int64(size) {
136 resp.Data = resp.Data[:size]
137 if len(resp.Data) == 0 {
140 torrentOff := fn.TorrentOffset + req.Offset
141 n, err := readFull(ctx, fn.FS, fn.t, torrentOff, resp.Data)
146 panic(fmt.Sprintf("%d < %d", n, size))
151 type dirNode struct {
156 _ fusefs.HandleReadDirAller = dirNode{}
157 _ fusefs.HandleReader = fileNode{}
160 func isSubPath(parent, child string) bool {
161 if len(parent) == 0 {
162 return len(child) > 0
164 if !strings.HasPrefix(child, parent) {
167 s := child[len(parent):]
174 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
175 names := map[string]bool{}
176 for _, fi := range dn.metadata.Files {
177 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
180 name := fi.Path[len(dn.path)]
188 if len(fi.Path) == len(dn.path)+1 {
189 de.Type = fuse.DT_File
191 de.Type = fuse.DT_Dir
193 des = append(des, de)
198 func (dn dirNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
199 var torrentOffset int64
200 for _, fi := range dn.metadata.Files {
201 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
202 torrentOffset += fi.Length
205 if fi.Path[len(dn.path)] != name {
206 torrentOffset += fi.Length
210 __node.path = path.Join(__node.path, name)
211 if len(fi.Path) == len(dn.path)+1 {
214 size: uint64(fi.Length),
215 TorrentOffset: torrentOffset,
218 _node = dirNode{__node}
228 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
229 attr.Mode = os.ModeDir | defaultMode
233 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
234 for _, t := range rn.fs.Client.Torrents() {
236 if t.Name() != name || info == nil {
245 _node = fileNode{__node, uint64(info.Length), 0}
247 _node = dirNode{__node}
257 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
258 for _, t := range rn.fs.Client.Torrents() {
263 dirents = append(dirents, fuse.Dirent{
265 Type: func() fuse.DirentType {
277 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
278 attr.Mode = os.ModeDir
282 // TODO(anacrolix): Why should rootNode implement this?
283 func (rn rootNode) Forget() {
287 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
288 return rootNode{tfs}, nil
291 func (tfs *TorrentFS) Destroy() {
294 case <-tfs.destroyed:
301 func New(cl *torrent.Client) *TorrentFS {
304 destroyed: make(chan struct{}),