github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/rc.go (about) 1 package vfs 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/rclone/rclone/fs" 12 "github.com/rclone/rclone/fs/cache" 13 "github.com/rclone/rclone/fs/rc" 14 ) 15 16 const getVFSHelp = ` 17 This command takes an "fs" parameter. If this parameter is not 18 supplied and if there is only one VFS in use then that VFS will be 19 used. If there is more than one VFS in use then the "fs" parameter 20 must be supplied.` 21 22 // GetVFS gets a VFS with config name "fs" from the cache or returns an error. 23 // 24 // If "fs" is not set and there is one and only one VFS in the active 25 // cache then it returns it. This is for backwards compatibility. 26 // 27 // This deletes the "fs" parameter from in if it is valid 28 func getVFS(in rc.Params) (vfs *VFS, err error) { 29 fsString, err := in.GetString("fs") 30 if rc.IsErrParamNotFound(err) { 31 var count int 32 vfs, count = activeCacheEntries() 33 if count == 1 { 34 return vfs, nil 35 } else if count == 0 { 36 return nil, errors.New(`no VFS active and "fs" parameter not supplied`) 37 } 38 return nil, errors.New(`more than one VFS active - need "fs" parameter`) 39 } else if err != nil { 40 return nil, err 41 } 42 activeMu.Lock() 43 defer activeMu.Unlock() 44 fsString = cache.Canonicalize(fsString) 45 activeVFS := active[fsString] 46 if len(activeVFS) == 0 { 47 return nil, fmt.Errorf("no VFS found with name %q", fsString) 48 } else if len(activeVFS) > 1 { 49 return nil, fmt.Errorf("more than one VFS active with name %q", fsString) 50 } 51 delete(in, "fs") // delete the fs parameter 52 return activeVFS[0], nil 53 } 54 55 func init() { 56 rc.Add(rc.Call{ 57 Path: "vfs/refresh", 58 Fn: rcRefresh, 59 Title: "Refresh the directory cache.", 60 Help: ` 61 This reads the directories for the specified paths and freshens the 62 directory cache. 63 64 If no paths are passed in then it will refresh the root directory. 65 66 rclone rc vfs/refresh 67 68 Otherwise pass directories in as dir=path. Any parameter key 69 starting with dir will refresh that directory, e.g. 70 71 rclone rc vfs/refresh dir=home/junk dir2=data/misc 72 73 If the parameter recursive=true is given the whole directory tree 74 will get refreshed. This refresh will use --fast-list if enabled. 75 ` + getVFSHelp, 76 }) 77 } 78 79 func rcRefresh(ctx context.Context, in rc.Params) (out rc.Params, err error) { 80 vfs, err := getVFS(in) 81 if err != nil { 82 return nil, err 83 } 84 85 root, err := vfs.Root() 86 if err != nil { 87 return nil, err 88 } 89 getDir := func(path string) (*Dir, error) { 90 path = strings.Trim(path, "/") 91 segments := strings.Split(path, "/") 92 var node Node = root 93 for _, s := range segments { 94 if dir, ok := node.(*Dir); ok { 95 node, err = dir.stat(s) 96 if err != nil { 97 return nil, err 98 } 99 } 100 } 101 if dir, ok := node.(*Dir); ok { 102 return dir, nil 103 } 104 return nil, EINVAL 105 } 106 107 recursive := false 108 { 109 const k = "recursive" 110 111 if v, ok := in[k]; ok { 112 s, ok := v.(string) 113 if !ok { 114 return out, fmt.Errorf("value must be string %q=%v", k, v) 115 } 116 recursive, err = strconv.ParseBool(s) 117 if err != nil { 118 return out, fmt.Errorf("invalid value %q=%v", k, v) 119 } 120 delete(in, k) 121 } 122 } 123 124 result := map[string]string{} 125 if len(in) == 0 { 126 if recursive { 127 err = root.readDirTree() 128 } else { 129 err = root.readDir() 130 } 131 if err != nil { 132 result[""] = err.Error() 133 } else { 134 result[""] = "OK" 135 } 136 } else { 137 for k, v := range in { 138 path, ok := v.(string) 139 if !ok { 140 return out, fmt.Errorf("value must be string %q=%v", k, v) 141 } 142 if strings.HasPrefix(k, "dir") { 143 dir, err := getDir(path) 144 if err != nil { 145 result[path] = err.Error() 146 } else { 147 if recursive { 148 err = dir.readDirTree() 149 } else { 150 err = dir.readDir() 151 } 152 if err != nil { 153 result[path] = err.Error() 154 } else { 155 result[path] = "OK" 156 } 157 } 158 } else { 159 return out, fmt.Errorf("unknown key %q", k) 160 } 161 } 162 } 163 out = rc.Params{ 164 "result": result, 165 } 166 return out, nil 167 } 168 169 // Add remote control for the VFS 170 func init() { 171 rc.Add(rc.Call{ 172 Path: "vfs/forget", 173 Fn: rcForget, 174 Title: "Forget files or directories in the directory cache.", 175 Help: ` 176 This forgets the paths in the directory cache causing them to be 177 re-read from the remote when needed. 178 179 If no paths are passed in then it will forget all the paths in the 180 directory cache. 181 182 rclone rc vfs/forget 183 184 Otherwise pass files or dirs in as file=path or dir=path. Any 185 parameter key starting with file will forget that file and any 186 starting with dir will forget that dir, e.g. 187 188 rclone rc vfs/forget file=hello file2=goodbye dir=home/junk 189 ` + getVFSHelp, 190 }) 191 } 192 193 func rcForget(ctx context.Context, in rc.Params) (out rc.Params, err error) { 194 vfs, err := getVFS(in) 195 if err != nil { 196 return nil, err 197 } 198 199 root, err := vfs.Root() 200 if err != nil { 201 return nil, err 202 } 203 204 forgotten := []string{} 205 if len(in) == 0 { 206 root.ForgetAll() 207 } else { 208 for k, v := range in { 209 path, ok := v.(string) 210 if !ok { 211 return out, fmt.Errorf("value must be string %q=%v", k, v) 212 } 213 path = strings.Trim(path, "/") 214 if strings.HasPrefix(k, "file") { 215 root.ForgetPath(path, fs.EntryObject) 216 } else if strings.HasPrefix(k, "dir") { 217 root.ForgetPath(path, fs.EntryDirectory) 218 } else { 219 return out, fmt.Errorf("unknown key %q", k) 220 } 221 forgotten = append(forgotten, path) 222 } 223 } 224 out = rc.Params{ 225 "forgotten": forgotten, 226 } 227 return out, nil 228 } 229 230 func getDuration(k string, v interface{}) (time.Duration, error) { 231 s, ok := v.(string) 232 if !ok { 233 return 0, fmt.Errorf("value must be string %q=%v", k, v) 234 } 235 interval, err := fs.ParseDuration(s) 236 if err != nil { 237 return 0, fmt.Errorf("parse duration: %w", err) 238 } 239 return interval, nil 240 } 241 242 func getInterval(in rc.Params) (time.Duration, bool, error) { 243 k := "interval" 244 v, ok := in[k] 245 if !ok { 246 return 0, false, nil 247 } 248 interval, err := getDuration(k, v) 249 if err != nil { 250 return 0, true, err 251 } 252 if interval < 0 { 253 return 0, true, errors.New("interval must be >= 0") 254 } 255 delete(in, k) 256 return interval, true, nil 257 } 258 259 func getTimeout(in rc.Params) (time.Duration, error) { 260 k := "timeout" 261 v, ok := in[k] 262 if !ok { 263 return 10 * time.Second, nil 264 } 265 timeout, err := getDuration(k, v) 266 if err != nil { 267 return 0, err 268 } 269 delete(in, k) 270 return timeout, nil 271 } 272 273 func getStatus(vfs *VFS, in rc.Params) (out rc.Params, err error) { 274 for k, v := range in { 275 return nil, fmt.Errorf("invalid parameter: %s=%s", k, v) 276 } 277 return rc.Params{ 278 "enabled": vfs.Opt.PollInterval != 0, 279 "supported": vfs.pollChan != nil, 280 "interval": map[string]interface{}{ 281 "raw": vfs.Opt.PollInterval, 282 "seconds": vfs.Opt.PollInterval / time.Second, 283 "string": vfs.Opt.PollInterval.String(), 284 }, 285 }, nil 286 } 287 288 func init() { 289 rc.Add(rc.Call{ 290 Path: "vfs/poll-interval", 291 Fn: rcPollInterval, 292 Title: "Get the status or update the value of the poll-interval option.", 293 Help: ` 294 Without any parameter given this returns the current status of the 295 poll-interval setting. 296 297 When the interval=duration parameter is set, the poll-interval value 298 is updated and the polling function is notified. 299 Setting interval=0 disables poll-interval. 300 301 rclone rc vfs/poll-interval interval=5m 302 303 The timeout=duration parameter can be used to specify a time to wait 304 for the current poll function to apply the new value. 305 If timeout is less or equal 0, which is the default, wait indefinitely. 306 307 The new poll-interval value will only be active when the timeout is 308 not reached. 309 310 If poll-interval is updated or disabled temporarily, some changes 311 might not get picked up by the polling function, depending on the 312 used remote. 313 ` + getVFSHelp, 314 }) 315 } 316 317 func rcPollInterval(ctx context.Context, in rc.Params) (out rc.Params, err error) { 318 vfs, err := getVFS(in) 319 if err != nil { 320 return nil, err 321 } 322 323 interval, intervalPresent, err := getInterval(in) 324 if err != nil { 325 return nil, err 326 } 327 timeout, err := getTimeout(in) 328 if err != nil { 329 return nil, err 330 } 331 for k, v := range in { 332 return nil, fmt.Errorf("invalid parameter: %s=%s", k, v) 333 } 334 if vfs.pollChan == nil { 335 return nil, errors.New("poll-interval is not supported by this remote") 336 } 337 338 if !intervalPresent { 339 return getStatus(vfs, in) 340 } 341 var timeoutHit bool 342 var timeoutChan <-chan time.Time 343 if timeout > 0 { 344 timer := time.NewTimer(timeout) 345 defer timer.Stop() 346 timeoutChan = timer.C 347 } 348 select { 349 case vfs.pollChan <- interval: 350 vfs.Opt.PollInterval = interval 351 case <-timeoutChan: 352 timeoutHit = true 353 } 354 out, err = getStatus(vfs, in) 355 if out != nil { 356 out["timeout"] = timeoutHit 357 } 358 return 359 } 360 361 func init() { 362 rc.Add(rc.Call{ 363 Path: "vfs/list", 364 Title: "List active VFSes.", 365 Help: ` 366 This lists the active VFSes. 367 368 It returns a list under the key "vfses" where the values are the VFS 369 names that could be passed to the other VFS commands in the "fs" 370 parameter.`, 371 Fn: rcList, 372 }) 373 } 374 375 func rcList(ctx context.Context, in rc.Params) (out rc.Params, err error) { 376 activeMu.Lock() 377 defer activeMu.Unlock() 378 var names = []string{} 379 for name, vfses := range active { 380 if len(vfses) == 1 { 381 names = append(names, name) 382 } else { 383 for i := range vfses { 384 names = append(names, fmt.Sprintf("%s[%d]", name, i)) 385 } 386 } 387 } 388 out = rc.Params{} 389 out["vfses"] = names 390 return out, nil 391 } 392 393 func init() { 394 rc.Add(rc.Call{ 395 Path: "vfs/stats", 396 Title: "Stats for a VFS.", 397 Help: ` 398 This returns stats for the selected VFS. 399 400 { 401 // Status of the disk cache - only present if --vfs-cache-mode > off 402 "diskCache": { 403 "bytesUsed": 0, 404 "erroredFiles": 0, 405 "files": 0, 406 "hashType": 1, 407 "outOfSpace": false, 408 "path": "/home/user/.cache/rclone/vfs/local/mnt/a", 409 "pathMeta": "/home/user/.cache/rclone/vfsMeta/local/mnt/a", 410 "uploadsInProgress": 0, 411 "uploadsQueued": 0 412 }, 413 "fs": "/mnt/a", 414 "inUse": 1, 415 // Status of the in memory metadata cache 416 "metadataCache": { 417 "dirs": 1, 418 "files": 0 419 }, 420 // Options as returned by options/get 421 "opt": { 422 "CacheMaxAge": 3600000000000, 423 // ... 424 "WriteWait": 1000000000 425 } 426 } 427 428 ` + getVFSHelp, 429 Fn: rcStats, 430 }) 431 } 432 433 func rcStats(ctx context.Context, in rc.Params) (out rc.Params, err error) { 434 vfs, err := getVFS(in) 435 if err != nil { 436 return nil, err 437 } 438 return vfs.Stats(), nil 439 }