10 "github.com/anacrolix/fuse"
11 fusefs "github.com/anacrolix/fuse/fs"
13 "github.com/anacrolix/torrent"
14 "github.com/anacrolix/torrent/metainfo"
21 var torrentfsReadRequests = expvar.NewInt("torrentfsReadRequests")
23 type TorrentFS struct {
24 Client *torrent.Client
25 destroyed chan struct{}
32 _ fusefs.FSDestroyer = &TorrentFS{}
34 _ fusefs.NodeForgetter = rootNode{}
35 _ fusefs.HandleReadDirAller = rootNode{}
36 _ fusefs.HandleReadDirAller = dirNode{}
39 // Is a directory node that lists all torrents and handles destruction of the
41 type rootNode struct {
47 metadata *metainfo.Info
56 var _ fusefs.HandleReadDirAller = dirNode{}
58 func isSubPath(parent, child string) bool {
62 if !strings.HasPrefix(child, parent) {
65 extra := child[len(parent):]
69 // Not just a file with more stuff on the end.
70 return extra[0] == '/'
73 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
74 names := map[string]bool{}
75 for _, fi := range dn.metadata.Files {
76 filePathname := strings.Join(fi.Path, "/")
77 if !isSubPath(dn.path, filePathname) {
84 dirPathname := strings.Split(dn.path, "/")
85 name = fi.Path[len(dirPathname)]
94 if len(fi.Path) == len(dn.path)+1 {
95 de.Type = fuse.DT_File
104 func (dn dirNode) Lookup(_ context.Context, name string) (fusefs.Node, error) {
106 var file *torrent.File
109 fullPath = dn.path + "/" + name
113 for _, f := range dn.t.Files() {
114 if f.DisplayPath() == fullPath {
117 if isSubPath(fullPath, f.DisplayPath()) {
123 if dir && file != nil {
124 panic("both dir and file")
127 return fileNode{n, file}, nil
130 return dirNode{n}, nil
132 return nil, fuse.ENOENT
135 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
136 attr.Mode = os.ModeDir | defaultMode
140 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
141 for _, t := range rn.fs.Client.Torrents() {
143 if t.Name() != name || info == nil {
152 _node = fileNode{__node, t.Files()[0]}
154 _node = dirNode{__node}
164 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
165 for _, t := range rn.fs.Client.Torrents() {
170 dirents = append(dirents, fuse.Dirent{
172 Type: func() fuse.DirentType {
184 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
185 attr.Mode = os.ModeDir | defaultMode
189 // TODO(anacrolix): Why should rootNode implement this?
190 func (rn rootNode) Forget() {
194 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
195 return rootNode{tfs}, nil
198 func (tfs *TorrentFS) Destroy() {
201 case <-tfs.destroyed:
208 func New(cl *torrent.Client) *TorrentFS {
211 destroyed: make(chan struct{}),