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 type fileNode struct {
62 func (fn fileNode) Attr(ctx context.Context, attr *fuse.Attr) error {
64 attr.Mode = defaultMode
68 func (n *node) fsPath() string {
69 return "/" + n.metadata.Name + "/" + n.path
72 func blockingRead(ctx context.Context, fs *TorrentFS, t torrent.Torrent, off int64, p []byte) (n int, err error) {
81 readDone := make(chan struct{})
85 _n, _err = r.ReadAt(p, off)
104 func readFull(ctx context.Context, fs *TorrentFS, t torrent.Torrent, off int64, p []byte) (n int, err error) {
107 nn, err = blockingRead(ctx, fs, t, off, p)
118 func (fn fileNode) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error {
119 torrentfsReadRequests.Add(1)
121 panic("read on directory")
124 fileLeft := int64(fn.size) - req.Offset
128 if fileLeft < int64(size) {
131 resp.Data = resp.Data[:size]
132 if len(resp.Data) == 0 {
135 torrentOff := fn.TorrentOffset + req.Offset
136 n, err := readFull(ctx, fn.FS, fn.t, torrentOff, resp.Data)
141 panic(fmt.Sprintf("%d < %d", n, size))
146 type dirNode struct {
151 _ fusefs.HandleReadDirAller = dirNode{}
152 _ fusefs.HandleReader = fileNode{}
155 func isSubPath(parent, child string) bool {
156 if !strings.HasPrefix(child, parent) {
159 s := child[len(parent):]
166 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
167 names := map[string]bool{}
168 for _, fi := range dn.metadata.Files {
169 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
172 name := fi.Path[len(dn.path)]
180 if len(fi.Path) == len(dn.path)+1 {
181 de.Type = fuse.DT_File
183 de.Type = fuse.DT_Dir
185 des = append(des, de)
190 func (dn dirNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
191 var torrentOffset int64
192 for _, fi := range dn.metadata.Files {
193 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
194 torrentOffset += fi.Length
197 if fi.Path[len(dn.path)] != name {
198 torrentOffset += fi.Length
202 __node.path = path.Join(__node.path, name)
203 if len(fi.Path) == len(dn.path)+1 {
206 size: uint64(fi.Length),
207 TorrentOffset: torrentOffset,
210 _node = dirNode{__node}
220 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
221 attr.Mode = os.ModeDir | defaultMode
225 func (me rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
226 for _, t := range me.fs.Client.Torrents() {
228 if t.Name() != name || info == nil {
237 _node = fileNode{__node, uint64(info.Length), 0}
239 _node = dirNode{__node}
249 func (me rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
250 for _, t := range me.fs.Client.Torrents() {
255 dirents = append(dirents, fuse.Dirent{
257 Type: func() fuse.DirentType {
269 func (rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
270 attr.Mode = os.ModeDir
274 // TODO(anacrolix): Why should rootNode implement this?
275 func (me rootNode) Forget() {
279 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
280 return rootNode{tfs}, nil
283 func (me *TorrentFS) Destroy() {
293 func New(cl *torrent.Client) *TorrentFS {
296 destroyed: make(chan struct{}),