]> Sergey Matveev's repositories - btrtrc.git/blob - fs/torrentfs.go
Big visibility/doc clean-up, and extract mmap_span package
[btrtrc.git] / fs / torrentfs.go
1 package torrentfs
2
3 import (
4         "log"
5         "os"
6
7         "bazil.org/fuse"
8         fusefs "bazil.org/fuse/fs"
9         "bitbucket.org/anacrolix/go.torrent"
10         metainfo "github.com/nsf/libtorgo/torrent"
11 )
12
13 const (
14         defaultMode = 0555
15 )
16
17 type torrentFS struct {
18         Client *torrent.Client
19 }
20
21 var _ fusefs.NodeForgetter = rootNode{}
22
23 type rootNode struct {
24         fs *torrentFS
25 }
26
27 type node struct {
28         path     []string
29         metaInfo *metainfo.MetaInfo
30         FS       *torrentFS
31         InfoHash torrent.InfoHash
32 }
33
34 type fileNode struct {
35         node
36         size          uint64
37         TorrentOffset int64
38 }
39
40 func (fn fileNode) Attr() (attr fuse.Attr) {
41         attr.Size = fn.size
42         attr.Mode = defaultMode
43         return
44 }
45
46 func (fn fileNode) Read(req *fuse.ReadRequest, resp *fuse.ReadResponse, intr fusefs.Intr) fuse.Error {
47         if req.Dir {
48                 panic("hodor")
49         }
50         data := make([]byte, func() int {
51                 _len := int64(fn.size) - req.Offset
52                 if int64(req.Size) < _len {
53                         return req.Size
54                 } else {
55                         // limit read to the end of the file
56                         return int(_len)
57                 }
58         }())
59         if len(data) == 0 {
60                 return nil
61         }
62         infoHash := torrent.BytesInfoHash(fn.metaInfo.InfoHash)
63         torrentOff := fn.TorrentOffset + req.Offset
64         log.Print(torrentOff, len(data), fn.TorrentOffset)
65         if err := fn.FS.Client.PrioritizeDataRegion(infoHash, torrentOff, int64(len(data))); err != nil {
66                 panic(err)
67         }
68         for {
69                 dataWaiter := fn.FS.Client.DataWaiter()
70                 n, err := fn.FS.Client.TorrentReadAt(infoHash, torrentOff, data)
71                 switch err {
72                 case nil:
73                         resp.Data = data[:n]
74                         return nil
75                 case torrent.ErrDataNotReady:
76                         select {
77                         case <-dataWaiter:
78                         case <-intr:
79                                 return fuse.EINTR
80                         }
81                 default:
82                         log.Print(err)
83                         return fuse.EIO
84                 }
85         }
86 }
87
88 type dirNode struct {
89         node
90 }
91
92 var (
93         _ fusefs.HandleReadDirer = dirNode{}
94
95         _ fusefs.HandleReader = fileNode{}
96 )
97
98 func isSubPath(parent, child []string) bool {
99         if len(child) <= len(parent) {
100                 return false
101         }
102         for i := range parent {
103                 if parent[i] != child[i] {
104                         return false
105                 }
106         }
107         return true
108 }
109
110 func (dn dirNode) ReadDir(intr fusefs.Intr) (des []fuse.Dirent, err fuse.Error) {
111         names := map[string]bool{}
112         for _, fi := range dn.metaInfo.Files {
113                 if !isSubPath(dn.path, fi.Path) {
114                         continue
115                 }
116                 name := fi.Path[len(dn.path)]
117                 if names[name] {
118                         continue
119                 }
120                 names[name] = true
121                 de := fuse.Dirent{
122                         Name: name,
123                 }
124                 if len(fi.Path) == len(dn.path)+1 {
125                         de.Type = fuse.DT_File
126                 } else {
127                         de.Type = fuse.DT_Dir
128                 }
129                 des = append(des, de)
130         }
131         return
132 }
133
134 func (dn dirNode) Lookup(name string, intr fusefs.Intr) (_node fusefs.Node, err fuse.Error) {
135         var torrentOffset int64
136         for _, fi := range dn.metaInfo.Files {
137                 if !isSubPath(dn.path, fi.Path) {
138                         torrentOffset += fi.Length
139                         continue
140                 }
141                 if fi.Path[len(dn.path)] != name {
142                         torrentOffset += fi.Length
143                         continue
144                 }
145                 __node := dn.node
146                 __node.path = append(__node.path, name)
147                 if len(fi.Path) == len(dn.path)+1 {
148                         _node = fileNode{
149                                 node:          __node,
150                                 size:          uint64(fi.Length),
151                                 TorrentOffset: torrentOffset,
152                         }
153                 } else {
154                         _node = dirNode{__node}
155                 }
156                 break
157         }
158         if _node == nil {
159                 err = fuse.ENOENT
160         }
161         return
162 }
163
164 func (dn dirNode) Attr() (attr fuse.Attr) {
165         attr.Mode = os.ModeDir | defaultMode
166         return
167 }
168
169 func isSingleFileTorrent(mi *metainfo.MetaInfo) bool {
170         return len(mi.Files) == 1 && mi.Files[0].Path == nil
171 }
172
173 func (me rootNode) Lookup(name string, intr fusefs.Intr) (_node fusefs.Node, err fuse.Error) {
174         for _, _torrent := range me.fs.Client.Torrents() {
175                 metaInfo := _torrent.MetaInfo
176                 if metaInfo.Name == name {
177                         __node := node{
178                                 metaInfo: metaInfo,
179                                 FS:       me.fs,
180                                 InfoHash: torrent.BytesInfoHash(metaInfo.InfoHash),
181                         }
182                         if isSingleFileTorrent(metaInfo) {
183                                 _node = fileNode{__node, uint64(metaInfo.Files[0].Length), 0}
184                         } else {
185                                 _node = dirNode{__node}
186                         }
187                         break
188                 }
189         }
190         if _node == nil {
191                 err = fuse.ENOENT
192         }
193         return
194 }
195
196 func (me rootNode) ReadDir(intr fusefs.Intr) (dirents []fuse.Dirent, err fuse.Error) {
197         for _, _torrent := range me.fs.Client.Torrents() {
198                 metaInfo := _torrent.MetaInfo
199                 dirents = append(dirents, fuse.Dirent{
200                         Name: metaInfo.Name,
201                         Type: func() fuse.DirentType {
202                                 if isSingleFileTorrent(metaInfo) {
203                                         return fuse.DT_File
204                                 } else {
205                                         return fuse.DT_Dir
206                                 }
207                         }(),
208                 })
209         }
210         return
211 }
212
213 func (rootNode) Attr() fuse.Attr {
214         return fuse.Attr{
215                 Mode: os.ModeDir,
216         }
217 }
218
219 // TODO(anacrolix): Why should rootNode implement this?
220 func (rootNode) Forget() {
221 }
222
223 func (tfs *torrentFS) Root() (fusefs.Node, fuse.Error) {
224         return rootNode{tfs}, nil
225 }
226
227 func New(cl *torrent.Client) *torrentFS {
228         fs := &torrentFS{
229                 Client: cl,
230         }
231         return fs
232 }