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