13 fusefs "bazil.org/fuse/fs"
14 "golang.org/x/net/context"
16 "github.com/anacrolix/torrent"
17 "github.com/anacrolix/torrent/metainfo"
25 torrentfsReadRequests = expvar.NewInt("torrentfsReadRequests")
26 torrentfsDelayedReadRequests = expvar.NewInt("torrentfsDelayedReadRequests")
27 interruptedReads = expvar.NewInt("interruptedReads")
30 type TorrentFS struct {
31 Client *torrent.Client
32 destroyed chan struct{}
39 _ fusefs.FSDestroyer = &TorrentFS{}
41 _ fusefs.NodeForgetter = rootNode{}
42 _ fusefs.HandleReadDirAller = rootNode{}
43 _ fusefs.HandleReadDirAller = dirNode{}
46 type rootNode struct {
52 metadata *metainfo.InfoEx
57 type fileNode struct {
63 func (fn fileNode) Attr(ctx context.Context, attr *fuse.Attr) error {
65 attr.Mode = defaultMode
69 func (n *node) fsPath() string {
70 return "/" + n.metadata.Name + "/" + n.path
73 func blockingRead(ctx context.Context, fs *TorrentFS, t torrent.Torrent, off int64, p []byte) (n int, err error) {
82 readDone := make(chan struct{})
87 _, _err = r.Seek(off, os.SEEK_SET)
91 _n, _err = io.ReadFull(r, p)
109 func readFull(ctx context.Context, fs *TorrentFS, t torrent.Torrent, off int64, p []byte) (n int, err error) {
112 nn, err = blockingRead(ctx, fs, t, off, p)
123 func (fn fileNode) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
124 torrentfsReadRequests.Add(1)
126 panic("read on directory")
129 fileLeft := int64(fn.size) - req.Offset
133 if fileLeft < int64(size) {
136 resp.Data = resp.Data[:size]
137 if len(resp.Data) == 0 {
140 torrentOff := fn.TorrentOffset + req.Offset
141 n, err := readFull(ctx, fn.FS, fn.t, torrentOff, resp.Data)
146 panic(fmt.Sprintf("%d < %d", n, size))
151 type dirNode struct {
156 _ fusefs.HandleReadDirAller = dirNode{}
157 _ fusefs.HandleReader = fileNode{}
160 func isSubPath(parent, child string) bool {
161 if !strings.HasPrefix(child, parent) {
164 s := child[len(parent):]
171 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
172 names := map[string]bool{}
173 for _, fi := range dn.metadata.Files {
174 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
177 name := fi.Path[len(dn.path)]
185 if len(fi.Path) == len(dn.path)+1 {
186 de.Type = fuse.DT_File
188 de.Type = fuse.DT_Dir
190 des = append(des, de)
195 func (dn dirNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
196 var torrentOffset int64
197 for _, fi := range dn.metadata.Files {
198 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
199 torrentOffset += fi.Length
202 if fi.Path[len(dn.path)] != name {
203 torrentOffset += fi.Length
207 __node.path = path.Join(__node.path, name)
208 if len(fi.Path) == len(dn.path)+1 {
211 size: uint64(fi.Length),
212 TorrentOffset: torrentOffset,
215 _node = dirNode{__node}
225 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
226 attr.Mode = os.ModeDir | defaultMode
230 func (me rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
231 for _, t := range me.fs.Client.Torrents() {
233 if t.Name() != name || info == nil {
242 _node = fileNode{__node, uint64(info.Length), 0}
244 _node = dirNode{__node}
254 func (me rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
255 for _, t := range me.fs.Client.Torrents() {
260 dirents = append(dirents, fuse.Dirent{
262 Type: func() fuse.DirentType {
274 func (rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
275 attr.Mode = os.ModeDir
279 // TODO(anacrolix): Why should rootNode implement this?
280 func (me rootNode) Forget() {
284 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
285 return rootNode{tfs}, nil
288 func (me *TorrentFS) Destroy() {
298 func New(cl *torrent.Client) *TorrentFS {
301 destroyed: make(chan struct{}),