13 fusefs "bazil.org/fuse/fs"
14 "bitbucket.org/anacrolix/go.torrent"
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{}
34 _ fusefs.FSDestroyer = &TorrentFS{}
35 _ fusefs.FSIniter = &TorrentFS{}
38 func (fs *TorrentFS) Init(req *fuse.InitRequest, resp *fuse.InitResponse, intr fusefs.Intr) fuse.Error {
41 resp.MaxReadahead = req.MaxReadahead
42 resp.Flags |= fuse.InitAsyncRead
46 var _ fusefs.NodeForgetter = rootNode{}
48 type rootNode struct {
54 metadata *torrent.MetaInfo
59 type fileNode struct {
65 func (fn fileNode) Attr() (attr fuse.Attr) {
67 attr.Mode = defaultMode
71 func (n *node) fsPath() string {
72 return "/" + strings.Join(append([]string{n.metadata.Name}, n.path...), "/")
75 func blockingRead(fs *TorrentFS, t torrent.Torrent, off int64, p []byte, intr fusefs.Intr) (n int, err fuse.Error) {
80 readDone := make(chan struct{})
82 _n, _err = t.ReadAt(p, off)
97 func readFull(fs *TorrentFS, t torrent.Torrent, off int64, p []byte, intr fusefs.Intr) (n int, err fuse.Error) {
100 nn, err = blockingRead(fs, t, off, p, intr)
111 func (fn fileNode) Read(req *fuse.ReadRequest, resp *fuse.ReadResponse, intr fusefs.Intr) fuse.Error {
112 torrentfsReadRequests.Add(1)
113 started := time.Now()
115 panic("read on directory")
118 ms := time.Now().Sub(started).Nanoseconds() / 1000000
122 log.Printf("torrentfs read took %dms", ms)
125 fileLeft := int64(fn.size) - req.Offset
129 if fileLeft < int64(size) {
132 resp.Data = resp.Data[:size]
133 if len(resp.Data) == 0 {
136 torrentOff := fn.TorrentOffset + req.Offset
137 n, err := readFull(fn.FS, fn.t, torrentOff, resp.Data, intr)
142 panic(fmt.Sprintf("%d < %d", n, size))
147 type dirNode struct {
152 _ fusefs.HandleReadDirer = dirNode{}
153 _ fusefs.HandleReader = fileNode{}
156 func isSubPath(parent, child []string) bool {
157 if len(child) <= len(parent) {
160 for i := range parent {
161 if parent[i] != child[i] {
168 func (dn dirNode) ReadDir(intr fusefs.Intr) (des []fuse.Dirent, err fuse.Error) {
169 names := map[string]bool{}
170 for _, fi := range dn.metadata.Files {
171 if !isSubPath(dn.path, fi.Path) {
174 name := fi.Path[len(dn.path)]
182 if len(fi.Path) == len(dn.path)+1 {
183 de.Type = fuse.DT_File
185 de.Type = fuse.DT_Dir
187 des = append(des, de)
192 func (dn dirNode) Lookup(name string, intr fusefs.Intr) (_node fusefs.Node, err fuse.Error) {
193 var torrentOffset int64
194 for _, fi := range dn.metadata.Files {
195 if !isSubPath(dn.path, fi.Path) {
196 torrentOffset += fi.Length
199 if fi.Path[len(dn.path)] != name {
200 torrentOffset += fi.Length
204 __node.path = append(__node.path, name)
205 if len(fi.Path) == len(dn.path)+1 {
208 size: uint64(fi.Length),
209 TorrentOffset: torrentOffset,
212 _node = dirNode{__node}
222 func (dn dirNode) Attr() (attr fuse.Attr) {
223 attr.Mode = os.ModeDir | defaultMode
227 func (me rootNode) Lookup(name string, intr fusefs.Intr) (_node fusefs.Node, err fuse.Error) {
228 for _, t := range me.fs.Client.Torrents() {
229 if t.Name() != name || t.Info == nil {
237 if t.Info.SingleFile() {
238 _node = fileNode{__node, uint64(t.Info.Length), 0}
240 _node = dirNode{__node}
250 func (me rootNode) ReadDir(intr fusefs.Intr) (dirents []fuse.Dirent, err fuse.Error) {
251 for _, t := range me.fs.Client.Torrents() {
255 dirents = append(dirents, fuse.Dirent{
257 Type: func() fuse.DirentType {
258 if t.Info.SingleFile() {
269 func (rootNode) Attr() fuse.Attr {
275 // TODO(anacrolix): Why should rootNode implement this?
276 func (me rootNode) Forget() {
280 func (tfs *TorrentFS) Root() (fusefs.Node, fuse.Error) {
281 return rootNode{tfs}, nil
284 func (me *TorrentFS) Destroy() {
294 func New(cl *torrent.Client) *TorrentFS {
297 destroyed: make(chan struct{}),