11 fusefs "bazil.org/fuse/fs"
12 "github.com/anacrolix/torrent"
13 "github.com/anacrolix/torrent/metainfo"
21 torrentfsReadRequests = expvar.NewInt("torrentfsReadRequests")
22 torrentfsDelayedReadRequests = expvar.NewInt("torrentfsDelayedReadRequests")
23 interruptedReads = expvar.NewInt("interruptedReads")
26 type TorrentFS struct {
27 Client *torrent.Client
28 destroyed chan struct{}
35 _ fusefs.FSDestroyer = &TorrentFS{}
37 _ fusefs.NodeForgetter = rootNode{}
38 _ fusefs.HandleReadDirAller = rootNode{}
39 _ fusefs.HandleReadDirAller = dirNode{}
42 // Is a directory node that lists all torrents and handles destruction of the
44 type rootNode struct {
50 metadata *metainfo.Info
60 _ fusefs.HandleReadDirAller = dirNode{}
63 func isSubPath(parent, child string) bool {
67 if !strings.HasPrefix(child, parent) {
70 extra := child[len(parent):]
74 // Not just a file with more stuff on the end.
75 return extra[0] == '/'
78 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
79 names := map[string]bool{}
80 for _, fi := range dn.metadata.Files {
81 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
84 name := fi.Path[len(dn.path)]
92 if len(fi.Path) == len(dn.path)+1 {
93 de.Type = fuse.DT_File
102 func (dn dirNode) Lookup(_ context.Context, name string) (fusefs.Node, error) {
104 var file *torrent.File
105 fullPath := dn.path + "/" + name
106 for _, f := range dn.t.Files() {
107 if f.DisplayPath() == fullPath {
110 if isSubPath(fullPath, f.DisplayPath()) {
116 if dir && file != nil {
117 panic("both dir and file")
120 return fileNode{n, file}, nil
123 return dirNode{n}, nil
125 return nil, fuse.ENOENT
128 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
129 attr.Mode = os.ModeDir | defaultMode
133 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
134 for _, t := range rn.fs.Client.Torrents() {
136 if t.Name() != name || info == nil {
145 _node = fileNode{__node, t.Files()[0]}
147 _node = dirNode{__node}
157 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
158 for _, t := range rn.fs.Client.Torrents() {
163 dirents = append(dirents, fuse.Dirent{
165 Type: func() fuse.DirentType {
177 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
178 attr.Mode = os.ModeDir
182 // TODO(anacrolix): Why should rootNode implement this?
183 func (rn rootNode) Forget() {
187 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
188 return rootNode{tfs}, nil
191 func (tfs *TorrentFS) Destroy() {
194 case <-tfs.destroyed:
201 func New(cl *torrent.Client) *TorrentFS {
204 destroyed: make(chan struct{}),