10 fusefs "bazil.org/fuse/fs"
11 "golang.org/x/net/context"
13 "github.com/anacrolix/torrent"
14 "github.com/anacrolix/torrent/metainfo"
22 torrentfsReadRequests = expvar.NewInt("torrentfsReadRequests")
23 torrentfsDelayedReadRequests = expvar.NewInt("torrentfsDelayedReadRequests")
24 interruptedReads = expvar.NewInt("interruptedReads")
27 type TorrentFS struct {
28 Client *torrent.Client
29 destroyed chan struct{}
36 _ fusefs.FSDestroyer = &TorrentFS{}
38 _ fusefs.NodeForgetter = rootNode{}
39 _ fusefs.HandleReadDirAller = rootNode{}
40 _ fusefs.HandleReadDirAller = dirNode{}
43 // Is a directory node that lists all torrents and handles destruction of the
45 type rootNode struct {
51 metadata *metainfo.Info
61 _ fusefs.HandleReadDirAller = dirNode{}
64 func isSubPath(parent, child string) bool {
68 if !strings.HasPrefix(child, parent) {
71 extra := child[len(parent):]
75 // Not just a file with more stuff on the end.
76 return extra[0] == '/'
79 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
80 names := map[string]bool{}
81 for _, fi := range dn.metadata.Files {
82 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
85 name := fi.Path[len(dn.path)]
93 if len(fi.Path) == len(dn.path)+1 {
94 de.Type = fuse.DT_File
103 func (dn dirNode) Lookup(_ context.Context, name string) (fusefs.Node, error) {
105 var file *torrent.File
106 fullPath := dn.path + "/" + name
107 for _, f := range dn.t.Files() {
108 if f.DisplayPath() == fullPath {
111 if isSubPath(fullPath, f.DisplayPath()) {
117 if dir && file != nil {
118 panic("both dir and file")
121 return fileNode{n, file}, nil
124 return dirNode{n}, nil
126 return nil, fuse.ENOENT
129 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
130 attr.Mode = os.ModeDir | defaultMode
134 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
135 for _, t := range rn.fs.Client.Torrents() {
137 if t.Name() != name || info == nil {
146 _node = fileNode{__node, t.Files()[0]}
148 _node = dirNode{__node}
158 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
159 for _, t := range rn.fs.Client.Torrents() {
164 dirents = append(dirents, fuse.Dirent{
166 Type: func() fuse.DirentType {
178 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
179 attr.Mode = os.ModeDir
183 // TODO(anacrolix): Why should rootNode implement this?
184 func (rn rootNode) Forget() {
188 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
189 return rootNode{tfs}, nil
192 func (tfs *TorrentFS) Destroy() {
195 case <-tfs.destroyed:
202 func New(cl *torrent.Client) *TorrentFS {
205 destroyed: make(chan struct{}),