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  }