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
107 fullPath = dn.path + "/" + name
111 for _, f := range dn.t.Files() {
112 if f.DisplayPath() == fullPath {
115 if isSubPath(fullPath, f.DisplayPath()) {
121 if dir && file != nil {
122 panic("both dir and file")
125 return fileNode{n, file}, nil
128 return dirNode{n}, nil
130 return nil, fuse.ENOENT
133 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
134 attr.Mode = os.ModeDir | defaultMode
138 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
139 for _, t := range rn.fs.Client.Torrents() {
141 if t.Name() != name || info == nil {
150 _node = fileNode{__node, t.Files()[0]}
152 _node = dirNode{__node}
162 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
163 for _, t := range rn.fs.Client.Torrents() {
168 dirents = append(dirents, fuse.Dirent{
170 Type: func() fuse.DirentType {
182 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
183 attr.Mode = os.ModeDir
187 // TODO(anacrolix): Why should rootNode implement this?
188 func (rn rootNode) Forget() {
192 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
193 return rootNode{tfs}, nil
196 func (tfs *TorrentFS) Destroy() {
199 case <-tfs.destroyed:
206 func New(cl *torrent.Client) *TorrentFS {
209 destroyed: make(chan struct{}),