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