]> Sergey Matveev's repositories - btrtrc.git/blob - fs/torrentfs.go
Add the fs test torrent to the repo
[btrtrc.git] / fs / torrentfs.go
1 package torrentfs
2
3 import (
4         "expvar"
5         "os"
6         "path"
7         "strings"
8         "sync"
9
10         "bazil.org/fuse"
11         fusefs "bazil.org/fuse/fs"
12         "golang.org/x/net/context"
13
14         "github.com/anacrolix/torrent"
15         "github.com/anacrolix/torrent/metainfo"
16 )
17
18 const (
19         defaultMode = 0555
20 )
21
22 var (
23         torrentfsReadRequests        = expvar.NewInt("torrentfsReadRequests")
24         torrentfsDelayedReadRequests = expvar.NewInt("torrentfsDelayedReadRequests")
25         interruptedReads             = expvar.NewInt("interruptedReads")
26 )
27
28 type TorrentFS struct {
29         Client       *torrent.Client
30         destroyed    chan struct{}
31         mu           sync.Mutex
32         blockedReads int
33         event        sync.Cond
34 }
35
36 var (
37         _ fusefs.FSDestroyer = &TorrentFS{}
38
39         _ fusefs.NodeForgetter      = rootNode{}
40         _ fusefs.HandleReadDirAller = rootNode{}
41         _ fusefs.HandleReadDirAller = dirNode{}
42 )
43
44 type rootNode struct {
45         fs *TorrentFS
46 }
47
48 type node struct {
49         path     string
50         metadata *metainfo.Info
51         FS       *TorrentFS
52         t        *torrent.Torrent
53 }
54
55 type dirNode struct {
56         node
57 }
58
59 var (
60         _ fusefs.HandleReadDirAller = dirNode{}
61 )
62
63 func isSubPath(parent, child string) bool {
64         if len(parent) == 0 {
65                 return len(child) > 0
66         }
67         if !strings.HasPrefix(child, parent) {
68                 return false
69         }
70         s := child[len(parent):]
71         if len(s) == 0 {
72                 return false
73         }
74         return s[0] == '/'
75 }
76
77 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
78         names := map[string]bool{}
79         for _, fi := range dn.metadata.Files {
80                 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
81                         continue
82                 }
83                 name := fi.Path[len(dn.path)]
84                 if names[name] {
85                         continue
86                 }
87                 names[name] = true
88                 de := fuse.Dirent{
89                         Name: name,
90                 }
91                 if len(fi.Path) == len(dn.path)+1 {
92                         de.Type = fuse.DT_File
93                 } else {
94                         de.Type = fuse.DT_Dir
95                 }
96                 des = append(des, de)
97         }
98         return
99 }
100
101 func (dn dirNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
102         var torrentOffset int64
103         for _, fi := range dn.metadata.Files {
104                 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
105                         torrentOffset += fi.Length
106                         continue
107                 }
108                 if fi.Path[len(dn.path)] != name {
109                         torrentOffset += fi.Length
110                         continue
111                 }
112                 __node := dn.node
113                 __node.path = path.Join(__node.path, name)
114                 if len(fi.Path) == len(dn.path)+1 {
115                         _node = fileNode{
116                                 node:          __node,
117                                 size:          uint64(fi.Length),
118                                 TorrentOffset: torrentOffset,
119                         }
120                 } else {
121                         _node = dirNode{__node}
122                 }
123                 break
124         }
125         if _node == nil {
126                 err = fuse.ENOENT
127         }
128         return
129 }
130
131 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
132         attr.Mode = os.ModeDir | defaultMode
133         return nil
134 }
135
136 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
137         for _, t := range rn.fs.Client.Torrents() {
138                 info := t.Info()
139                 if t.Name() != name || info == nil {
140                         continue
141                 }
142                 __node := node{
143                         metadata: info,
144                         FS:       rn.fs,
145                         t:        t,
146                 }
147                 if !info.IsDir() {
148                         _node = fileNode{__node, uint64(info.Length), 0}
149                 } else {
150                         _node = dirNode{__node}
151                 }
152                 break
153         }
154         if _node == nil {
155                 err = fuse.ENOENT
156         }
157         return
158 }
159
160 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
161         for _, t := range rn.fs.Client.Torrents() {
162                 info := t.Info()
163                 if info == nil {
164                         continue
165                 }
166                 dirents = append(dirents, fuse.Dirent{
167                         Name: info.Name,
168                         Type: func() fuse.DirentType {
169                                 if !info.IsDir() {
170                                         return fuse.DT_File
171                                 } else {
172                                         return fuse.DT_Dir
173                                 }
174                         }(),
175                 })
176         }
177         return
178 }
179
180 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
181         attr.Mode = os.ModeDir
182         return nil
183 }
184
185 // TODO(anacrolix): Why should rootNode implement this?
186 func (rn rootNode) Forget() {
187         rn.fs.Destroy()
188 }
189
190 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
191         return rootNode{tfs}, nil
192 }
193
194 func (tfs *TorrentFS) Destroy() {
195         tfs.mu.Lock()
196         select {
197         case <-tfs.destroyed:
198         default:
199                 close(tfs.destroyed)
200         }
201         tfs.mu.Unlock()
202 }
203
204 func New(cl *torrent.Client) *TorrentFS {
205         fs := &TorrentFS{
206                 Client:    cl,
207                 destroyed: make(chan struct{}),
208         }
209         fs.event.L = &fs.mu
210         return fs
211 }