]> Sergey Matveev's repositories - btrtrc.git/blob - fs/torrentfs.go
goimports -local
[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                 filePathname := strings.Join(fi.Path, "/")
83                 if !isSubPath(dn.path, filePathname) {
84                         continue
85                 }
86                 var name string
87                 if dn.path == "" {
88                         name = fi.Path[0]
89                 } else {
90                         dirPathname := strings.Split(dn.path, "/")
91                         name = fi.Path[len(dirPathname)]
92                 }
93                 if names[name] {
94                         continue
95                 }
96                 names[name] = true
97                 de := fuse.Dirent{
98                         Name: name,
99                 }
100                 if len(fi.Path) == len(dn.path)+1 {
101                         de.Type = fuse.DT_File
102                 } else {
103                         de.Type = fuse.DT_Dir
104                 }
105                 des = append(des, de)
106         }
107         return
108 }
109
110 func (dn dirNode) Lookup(_ context.Context, name string) (fusefs.Node, error) {
111         dir := false
112         var file *torrent.File
113         var fullPath string
114         if dn.path != "" {
115                 fullPath = dn.path + "/" + name
116         } else {
117                 fullPath = name
118         }
119         for _, f := range dn.t.Files() {
120                 if f.DisplayPath() == fullPath {
121                         file = f
122                 }
123                 if isSubPath(fullPath, f.DisplayPath()) {
124                         dir = true
125                 }
126         }
127         n := dn.node
128         n.path = fullPath
129         if dir && file != nil {
130                 panic("both dir and file")
131         }
132         if file != nil {
133                 return fileNode{n, file}, nil
134         }
135         if dir {
136                 return dirNode{n}, nil
137         }
138         return nil, fuse.ENOENT
139 }
140
141 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
142         attr.Mode = os.ModeDir | defaultMode
143         return nil
144 }
145
146 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
147         for _, t := range rn.fs.Client.Torrents() {
148                 info := t.Info()
149                 if t.Name() != name || info == nil {
150                         continue
151                 }
152                 __node := node{
153                         metadata: info,
154                         FS:       rn.fs,
155                         t:        t,
156                 }
157                 if !info.IsDir() {
158                         _node = fileNode{__node, t.Files()[0]}
159                 } else {
160                         _node = dirNode{__node}
161                 }
162                 break
163         }
164         if _node == nil {
165                 err = fuse.ENOENT
166         }
167         return
168 }
169
170 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
171         for _, t := range rn.fs.Client.Torrents() {
172                 info := t.Info()
173                 if info == nil {
174                         continue
175                 }
176                 dirents = append(dirents, fuse.Dirent{
177                         Name: info.Name,
178                         Type: func() fuse.DirentType {
179                                 if !info.IsDir() {
180                                         return fuse.DT_File
181                                 } else {
182                                         return fuse.DT_Dir
183                                 }
184                         }(),
185                 })
186         }
187         return
188 }
189
190 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
191         attr.Mode = os.ModeDir
192         return nil
193 }
194
195 // TODO(anacrolix): Why should rootNode implement this?
196 func (rn rootNode) Forget() {
197         rn.fs.Destroy()
198 }
199
200 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
201         return rootNode{tfs}, nil
202 }
203
204 func (tfs *TorrentFS) Destroy() {
205         tfs.mu.Lock()
206         select {
207         case <-tfs.destroyed:
208         default:
209                 close(tfs.destroyed)
210         }
211         tfs.mu.Unlock()
212 }
213
214 func New(cl *torrent.Client) *TorrentFS {
215         fs := &TorrentFS{
216                 Client:    cl,
217                 destroyed: make(chan struct{}),
218         }
219         fs.event.L = &fs.mu
220         return fs
221 }