11 fusefs "bazil.org/fuse/fs"
13 "github.com/anacrolix/torrent"
14 "github.com/anacrolix/torrent/metainfo"
22 torrentfsReadRequests = expvar.NewInt("torrentfsReadRequests")
25 type TorrentFS struct {
26 Client *torrent.Client
27 destroyed chan struct{}
34 _ fusefs.FSDestroyer = &TorrentFS{}
36 _ fusefs.NodeForgetter = rootNode{}
37 _ fusefs.HandleReadDirAller = rootNode{}
38 _ fusefs.HandleReadDirAller = dirNode{}
41 // Is a directory node that lists all torrents and handles destruction of the
43 type rootNode struct {
49 metadata *metainfo.Info
59 _ fusefs.HandleReadDirAller = dirNode{}
62 func isSubPath(parent, child string) bool {
66 if !strings.HasPrefix(child, parent) {
69 extra := child[len(parent):]
73 // Not just a file with more stuff on the end.
74 return extra[0] == '/'
77 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
78 names := map[string]bool{}
79 for _, fi := range dn.metadata.Files {
80 filePathname := strings.Join(fi.Path, "/")
81 if !isSubPath(dn.path, filePathname) {
88 dirPathname := strings.Split(dn.path, "/")
89 name = fi.Path[len(dirPathname)]
98 if len(fi.Path) == len(dn.path)+1 {
99 de.Type = fuse.DT_File
101 de.Type = fuse.DT_Dir
103 des = append(des, de)
108 func (dn dirNode) Lookup(_ context.Context, name string) (fusefs.Node, error) {
110 var file *torrent.File
113 fullPath = dn.path + "/" + name
117 for _, f := range dn.t.Files() {
118 if f.DisplayPath() == fullPath {
121 if isSubPath(fullPath, f.DisplayPath()) {
127 if dir && file != nil {
128 panic("both dir and file")
131 return fileNode{n, file}, nil
134 return dirNode{n}, nil
136 return nil, fuse.ENOENT
139 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
140 attr.Mode = os.ModeDir | defaultMode
144 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
145 for _, t := range rn.fs.Client.Torrents() {
147 if t.Name() != name || info == nil {
156 _node = fileNode{__node, t.Files()[0]}
158 _node = dirNode{__node}
168 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
169 for _, t := range rn.fs.Client.Torrents() {
174 dirents = append(dirents, fuse.Dirent{
176 Type: func() fuse.DirentType {
188 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
189 attr.Mode = os.ModeDir
193 // TODO(anacrolix): Why should rootNode implement this?
194 func (rn rootNode) Forget() {
198 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
199 return rootNode{tfs}, nil
202 func (tfs *TorrentFS) Destroy() {
205 case <-tfs.destroyed:
212 func New(cl *torrent.Client) *TorrentFS {
215 destroyed: make(chan struct{}),