12 fusefs "bazil.org/fuse/fs"
13 "golang.org/x/net/context"
15 "github.com/anacrolix/torrent"
16 "github.com/anacrolix/torrent/metainfo"
24 torrentfsReadRequests = expvar.NewInt("torrentfsReadRequests")
25 torrentfsDelayedReadRequests = expvar.NewInt("torrentfsDelayedReadRequests")
26 interruptedReads = expvar.NewInt("interruptedReads")
29 type TorrentFS struct {
30 Client *torrent.Client
31 destroyed chan struct{}
38 _ fusefs.FSDestroyer = &TorrentFS{}
40 _ fusefs.NodeForgetter = rootNode{}
41 _ fusefs.HandleReadDirAller = rootNode{}
42 _ fusefs.HandleReadDirAller = dirNode{}
45 type rootNode struct {
51 metadata *metainfo.Info
56 func (n *node) fsPath() string {
57 return "/" + n.metadata.Name + "/" + n.path
60 func blockingRead(ctx context.Context, fs *TorrentFS, t *torrent.Torrent, off int64, p []byte) (n int, err error) {
69 readDone := make(chan struct{})
74 _, _err = r.Seek(off, os.SEEK_SET)
78 _n, _err = io.ReadFull(r, p)
96 func readFull(ctx context.Context, fs *TorrentFS, t *torrent.Torrent, off int64, p []byte) (n int, err error) {
99 nn, err = blockingRead(ctx, fs, t, off, p)
110 type dirNode struct {
115 _ fusefs.HandleReadDirAller = dirNode{}
118 func isSubPath(parent, child string) bool {
119 if len(parent) == 0 {
120 return len(child) > 0
122 if !strings.HasPrefix(child, parent) {
125 s := child[len(parent):]
132 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
133 names := map[string]bool{}
134 for _, fi := range dn.metadata.Files {
135 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
138 name := fi.Path[len(dn.path)]
146 if len(fi.Path) == len(dn.path)+1 {
147 de.Type = fuse.DT_File
149 de.Type = fuse.DT_Dir
151 des = append(des, de)
156 func (dn dirNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
157 var torrentOffset int64
158 for _, fi := range dn.metadata.Files {
159 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
160 torrentOffset += fi.Length
163 if fi.Path[len(dn.path)] != name {
164 torrentOffset += fi.Length
168 __node.path = path.Join(__node.path, name)
169 if len(fi.Path) == len(dn.path)+1 {
172 size: uint64(fi.Length),
173 TorrentOffset: torrentOffset,
176 _node = dirNode{__node}
186 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
187 attr.Mode = os.ModeDir | defaultMode
191 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
192 for _, t := range rn.fs.Client.Torrents() {
194 if t.Name() != name || info == nil {
203 _node = fileNode{__node, uint64(info.Length), 0}
205 _node = dirNode{__node}
215 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
216 for _, t := range rn.fs.Client.Torrents() {
221 dirents = append(dirents, fuse.Dirent{
223 Type: func() fuse.DirentType {
235 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
236 attr.Mode = os.ModeDir
240 // TODO(anacrolix): Why should rootNode implement this?
241 func (rn rootNode) Forget() {
245 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
246 return rootNode{tfs}, nil
249 func (tfs *TorrentFS) Destroy() {
252 case <-tfs.destroyed:
259 func New(cl *torrent.Client) *TorrentFS {
262 destroyed: make(chan struct{}),