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