11 fusefs "bazil.org/fuse/fs"
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 filePathname := strings.Join(fi.Path, "/")
83 if !isSubPath(dn.path, filePathname) {
90 dirPathname := strings.Split(dn.path, "/")
91 name = fi.Path[len(dirPathname)]
100 if len(fi.Path) == len(dn.path)+1 {
101 de.Type = fuse.DT_File
103 de.Type = fuse.DT_Dir
105 des = append(des, de)
110 func (dn dirNode) Lookup(_ context.Context, name string) (fusefs.Node, error) {
112 var file *torrent.File
115 fullPath = dn.path + "/" + name
119 for _, f := range dn.t.Files() {
120 if f.DisplayPath() == fullPath {
123 if isSubPath(fullPath, f.DisplayPath()) {
129 if dir && file != nil {
130 panic("both dir and file")
133 return fileNode{n, file}, nil
136 return dirNode{n}, nil
138 return nil, fuse.ENOENT
141 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
142 attr.Mode = os.ModeDir | defaultMode
146 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
147 for _, t := range rn.fs.Client.Torrents() {
149 if t.Name() != name || info == nil {
158 _node = fileNode{__node, t.Files()[0]}
160 _node = dirNode{__node}
170 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
171 for _, t := range rn.fs.Client.Torrents() {
176 dirents = append(dirents, fuse.Dirent{
178 Type: func() fuse.DirentType {
190 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
191 attr.Mode = os.ModeDir
195 // TODO(anacrolix): Why should rootNode implement this?
196 func (rn rootNode) Forget() {
200 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
201 return rootNode{tfs}, nil
204 func (tfs *TorrentFS) Destroy() {
207 case <-tfs.destroyed:
214 func New(cl *torrent.Client) *TorrentFS {
217 destroyed: make(chan struct{}),