]> Sergey Matveev's repositories - btrtrc.git/blob - fs/torrentfs.go
0d9244b803c2a054bf6a8dc7178ff76d82c5267f
[btrtrc.git] / fs / torrentfs.go
1 package torrentfs
2
3 import (
4         "expvar"
5         "io"
6         "os"
7         "path"
8         "strings"
9         "sync"
10
11         "bazil.org/fuse"
12         fusefs "bazil.org/fuse/fs"
13         "golang.org/x/net/context"
14
15         "github.com/anacrolix/torrent"
16         "github.com/anacrolix/torrent/metainfo"
17 )
18
19 const (
20         defaultMode = 0555
21 )
22
23 var (
24         torrentfsReadRequests        = expvar.NewInt("torrentfsReadRequests")
25         torrentfsDelayedReadRequests = expvar.NewInt("torrentfsDelayedReadRequests")
26         interruptedReads             = expvar.NewInt("interruptedReads")
27 )
28
29 type TorrentFS struct {
30         Client       *torrent.Client
31         destroyed    chan struct{}
32         mu           sync.Mutex
33         blockedReads int
34         event        sync.Cond
35 }
36
37 var (
38         _ fusefs.FSDestroyer = &TorrentFS{}
39
40         _ fusefs.NodeForgetter      = rootNode{}
41         _ fusefs.HandleReadDirAller = rootNode{}
42         _ fusefs.HandleReadDirAller = dirNode{}
43 )
44
45 type rootNode struct {
46         fs *TorrentFS
47 }
48
49 type node struct {
50         path     string
51         metadata *metainfo.Info
52         FS       *TorrentFS
53         t        *torrent.Torrent
54 }
55
56 func (n *node) fsPath() string {
57         return "/" + n.metadata.Name + "/" + n.path
58 }
59
60 func blockingRead(ctx context.Context, fs *TorrentFS, t *torrent.Torrent, off int64, p []byte) (n int, err error) {
61         fs.mu.Lock()
62         fs.blockedReads++
63         fs.event.Broadcast()
64         fs.mu.Unlock()
65         var (
66                 _n   int
67                 _err error
68         )
69         readDone := make(chan struct{})
70         go func() {
71                 defer close(readDone)
72                 r := t.NewReader()
73                 defer r.Close()
74                 _, _err = r.Seek(off, os.SEEK_SET)
75                 if _err != nil {
76                         return
77                 }
78                 _n, _err = io.ReadFull(r, p)
79         }()
80         select {
81         case <-readDone:
82                 n = _n
83                 err = _err
84         case <-fs.destroyed:
85                 err = fuse.EIO
86         case <-ctx.Done():
87                 err = fuse.EINTR
88         }
89         fs.mu.Lock()
90         fs.blockedReads--
91         fs.event.Broadcast()
92         fs.mu.Unlock()
93         return
94 }
95
96 func readFull(ctx context.Context, fs *TorrentFS, t *torrent.Torrent, off int64, p []byte) (n int, err error) {
97         for len(p) != 0 {
98                 var nn int
99                 nn, err = blockingRead(ctx, fs, t, off, p)
100                 if err != nil {
101                         break
102                 }
103                 n += nn
104                 off += int64(nn)
105                 p = p[nn:]
106         }
107         return
108 }
109
110 type dirNode struct {
111         node
112 }
113
114 var (
115         _ fusefs.HandleReadDirAller = dirNode{}
116 )
117
118 func isSubPath(parent, child string) bool {
119         if len(parent) == 0 {
120                 return len(child) > 0
121         }
122         if !strings.HasPrefix(child, parent) {
123                 return false
124         }
125         s := child[len(parent):]
126         if len(s) == 0 {
127                 return false
128         }
129         return s[0] == '/'
130 }
131
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, "/")) {
136                         continue
137                 }
138                 name := fi.Path[len(dn.path)]
139                 if names[name] {
140                         continue
141                 }
142                 names[name] = true
143                 de := fuse.Dirent{
144                         Name: name,
145                 }
146                 if len(fi.Path) == len(dn.path)+1 {
147                         de.Type = fuse.DT_File
148                 } else {
149                         de.Type = fuse.DT_Dir
150                 }
151                 des = append(des, de)
152         }
153         return
154 }
155
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
161                         continue
162                 }
163                 if fi.Path[len(dn.path)] != name {
164                         torrentOffset += fi.Length
165                         continue
166                 }
167                 __node := dn.node
168                 __node.path = path.Join(__node.path, name)
169                 if len(fi.Path) == len(dn.path)+1 {
170                         _node = fileNode{
171                                 node:          __node,
172                                 size:          uint64(fi.Length),
173                                 TorrentOffset: torrentOffset,
174                         }
175                 } else {
176                         _node = dirNode{__node}
177                 }
178                 break
179         }
180         if _node == nil {
181                 err = fuse.ENOENT
182         }
183         return
184 }
185
186 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
187         attr.Mode = os.ModeDir | defaultMode
188         return nil
189 }
190
191 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
192         for _, t := range rn.fs.Client.Torrents() {
193                 info := t.Info()
194                 if t.Name() != name || info == nil {
195                         continue
196                 }
197                 __node := node{
198                         metadata: info,
199                         FS:       rn.fs,
200                         t:        t,
201                 }
202                 if !info.IsDir() {
203                         _node = fileNode{__node, uint64(info.Length), 0}
204                 } else {
205                         _node = dirNode{__node}
206                 }
207                 break
208         }
209         if _node == nil {
210                 err = fuse.ENOENT
211         }
212         return
213 }
214
215 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
216         for _, t := range rn.fs.Client.Torrents() {
217                 info := t.Info()
218                 if info == nil {
219                         continue
220                 }
221                 dirents = append(dirents, fuse.Dirent{
222                         Name: info.Name,
223                         Type: func() fuse.DirentType {
224                                 if !info.IsDir() {
225                                         return fuse.DT_File
226                                 } else {
227                                         return fuse.DT_Dir
228                                 }
229                         }(),
230                 })
231         }
232         return
233 }
234
235 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
236         attr.Mode = os.ModeDir
237         return nil
238 }
239
240 // TODO(anacrolix): Why should rootNode implement this?
241 func (rn rootNode) Forget() {
242         rn.fs.Destroy()
243 }
244
245 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
246         return rootNode{tfs}, nil
247 }
248
249 func (tfs *TorrentFS) Destroy() {
250         tfs.mu.Lock()
251         select {
252         case <-tfs.destroyed:
253         default:
254                 close(tfs.destroyed)
255         }
256         tfs.mu.Unlock()
257 }
258
259 func New(cl *torrent.Client) *TorrentFS {
260         fs := &TorrentFS{
261                 Client:    cl,
262                 destroyed: make(chan struct{}),
263         }
264         fs.event.L = &fs.mu
265         return fs
266 }