2021-06-14 11:42:49 +00:00
|
|
|
// NewFs and its helpers
|
|
|
|
|
|
|
|
package fs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/md5"
|
|
|
|
"encoding/base64"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2023-04-28 10:58:49 +00:00
|
|
|
"sync"
|
2021-06-14 11:42:49 +00:00
|
|
|
|
|
|
|
"github.com/rclone/rclone/fs/config/configmap"
|
|
|
|
"github.com/rclone/rclone/fs/fspath"
|
|
|
|
)
|
|
|
|
|
2023-04-28 10:58:49 +00:00
|
|
|
// Store the hashes of the overridden config
|
|
|
|
var (
|
|
|
|
overriddenConfigMu sync.Mutex
|
|
|
|
overriddenConfig = make(map[string]string)
|
|
|
|
)
|
|
|
|
|
2021-06-14 11:42:49 +00:00
|
|
|
// NewFs makes a new Fs object from the path
|
|
|
|
//
|
|
|
|
// The path is of the form remote:path
|
|
|
|
//
|
|
|
|
// Remotes are looked up in the config file. If the remote isn't
|
|
|
|
// found then NotFoundInConfigFile will be returned.
|
|
|
|
//
|
|
|
|
// On Windows avoid single character remote names as they can be mixed
|
|
|
|
// up with drive letters.
|
|
|
|
func NewFs(ctx context.Context, path string) (Fs, error) {
|
|
|
|
Debugf(nil, "Creating backend with remote %q", path)
|
2022-09-12 19:43:53 +00:00
|
|
|
if ConfigFileHasSection(path) {
|
|
|
|
Logf(nil, "%q refers to a local folder, use %q to refer to your remote or %q to hide this warning", path, path+":", "./"+path)
|
|
|
|
}
|
2021-06-14 11:42:49 +00:00
|
|
|
fsInfo, configName, fsPath, config, err := ConfigFs(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
overridden := fsInfo.Options.Overridden(config)
|
|
|
|
if len(overridden) > 0 {
|
|
|
|
extraConfig := overridden.String()
|
2022-08-14 02:56:32 +00:00
|
|
|
//Debugf(nil, "detected overridden config %q", extraConfig)
|
2021-06-14 11:42:49 +00:00
|
|
|
md5sumBinary := md5.Sum([]byte(extraConfig))
|
2023-04-28 10:58:49 +00:00
|
|
|
configHash := base64.RawURLEncoding.EncodeToString(md5sumBinary[:])
|
2021-06-14 11:42:49 +00:00
|
|
|
// 5 characters length is 5*6 = 30 bits of base64
|
2023-04-28 10:58:49 +00:00
|
|
|
overriddenConfigMu.Lock()
|
|
|
|
var suffix string
|
|
|
|
for maxLength := 5; ; maxLength++ {
|
|
|
|
suffix = "{" + configHash[:maxLength] + "}"
|
|
|
|
existingExtraConfig, ok := overriddenConfig[suffix]
|
|
|
|
if !ok || existingExtraConfig == extraConfig {
|
|
|
|
break
|
|
|
|
}
|
2021-06-14 11:42:49 +00:00
|
|
|
}
|
|
|
|
Debugf(configName, "detected overridden config - adding %q suffix to name", suffix)
|
|
|
|
// Add the suffix to the config name
|
|
|
|
//
|
|
|
|
// These need to work as filesystem names as the VFS cache will use them
|
|
|
|
configName += suffix
|
2023-04-28 10:58:49 +00:00
|
|
|
// Store the config suffixes for reversing in ConfigString
|
|
|
|
overriddenConfig[suffix] = extraConfig
|
|
|
|
overriddenConfigMu.Unlock()
|
2021-06-14 11:42:49 +00:00
|
|
|
}
|
2022-07-04 08:25:10 +00:00
|
|
|
f, err := fsInfo.NewFs(ctx, configName, fsPath, config)
|
|
|
|
if f != nil && (err == nil || err == ErrorIsFile) {
|
|
|
|
addReverse(f, fsInfo)
|
|
|
|
}
|
|
|
|
return f, err
|
2021-06-14 11:42:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ConfigFs makes the config for calling NewFs with.
|
|
|
|
//
|
|
|
|
// It parses the path which is of the form remote:path
|
|
|
|
//
|
|
|
|
// Remotes are looked up in the config file. If the remote isn't
|
|
|
|
// found then NotFoundInConfigFile will be returned.
|
|
|
|
func ConfigFs(path string) (fsInfo *RegInfo, configName, fsPath string, config *configmap.Map, err error) {
|
|
|
|
// Parse the remote path
|
|
|
|
fsInfo, configName, fsPath, connectionStringConfig, err := ParseRemote(path)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2024-07-01 17:06:49 +00:00
|
|
|
config = ConfigMap(fsInfo.Prefix, fsInfo.Options, configName, connectionStringConfig)
|
2021-06-14 11:42:49 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseRemote deconstructs a path into configName, fsPath, looking up
|
|
|
|
// the fsName in the config file (returning NotFoundInConfigFile if not found)
|
|
|
|
func ParseRemote(path string) (fsInfo *RegInfo, configName, fsPath string, connectionStringConfig configmap.Simple, err error) {
|
|
|
|
parsed, err := fspath.Parse(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, "", "", nil, err
|
|
|
|
}
|
|
|
|
configName, fsPath = parsed.Name, parsed.Path
|
|
|
|
var fsName string
|
|
|
|
var ok bool
|
|
|
|
if configName != "" {
|
|
|
|
if strings.HasPrefix(configName, ":") {
|
|
|
|
fsName = configName[1:]
|
|
|
|
} else {
|
2024-07-01 17:06:49 +00:00
|
|
|
m := ConfigMap("", nil, configName, parsed.Config)
|
2021-06-14 11:42:49 +00:00
|
|
|
fsName, ok = m.Get("type")
|
|
|
|
if !ok {
|
|
|
|
return nil, "", "", nil, ErrorNotFoundInConfigFile
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fsName = "local"
|
|
|
|
configName = "local"
|
|
|
|
}
|
|
|
|
fsInfo, err = Find(fsName)
|
|
|
|
return fsInfo, configName, fsPath, parsed.Config, err
|
|
|
|
}
|
|
|
|
|
2023-06-20 16:38:17 +00:00
|
|
|
// configString returns a canonical version of the config string used
|
2021-06-14 11:42:49 +00:00
|
|
|
// to configure the Fs as passed to fs.NewFs
|
2023-09-02 17:13:42 +00:00
|
|
|
func configString(f Info, full bool) string {
|
2021-06-14 11:42:49 +00:00
|
|
|
name := f.Name()
|
2023-06-20 16:38:17 +00:00
|
|
|
if open := strings.IndexRune(name, '{'); full && open >= 0 && strings.HasSuffix(name, "}") {
|
2023-04-28 10:58:49 +00:00
|
|
|
suffix := name[open:]
|
|
|
|
overriddenConfigMu.Lock()
|
|
|
|
config, ok := overriddenConfig[suffix]
|
|
|
|
overriddenConfigMu.Unlock()
|
|
|
|
if ok {
|
|
|
|
name = name[:open] + "," + config
|
|
|
|
} else {
|
|
|
|
Errorf(f, "Failed to find config for suffix %q", suffix)
|
|
|
|
}
|
|
|
|
}
|
2021-06-14 11:42:49 +00:00
|
|
|
root := f.Root()
|
|
|
|
if name == "local" && f.Features().IsLocal {
|
|
|
|
return root
|
|
|
|
}
|
|
|
|
return name + ":" + root
|
|
|
|
}
|
|
|
|
|
2023-06-20 16:38:17 +00:00
|
|
|
// ConfigString returns a canonical version of the config string used
|
|
|
|
// to configure the Fs as passed to fs.NewFs. For Fs with extra
|
|
|
|
// parameters this will include a canonical {hexstring} suffix.
|
2023-09-02 17:13:42 +00:00
|
|
|
func ConfigString(f Info) string {
|
2023-06-20 16:38:17 +00:00
|
|
|
return configString(f, false)
|
|
|
|
}
|
|
|
|
|
2023-09-02 17:13:42 +00:00
|
|
|
// FullPath returns the full path with remote:path/to/object
|
|
|
|
// for an object.
|
|
|
|
func FullPath(o Object) string {
|
|
|
|
return fspath.JoinRootPath(ConfigString(o.Fs()), o.Remote())
|
|
|
|
}
|
|
|
|
|
2023-06-20 16:38:17 +00:00
|
|
|
// ConfigStringFull returns a canonical version of the config string
|
|
|
|
// used to configure the Fs as passed to fs.NewFs. This string can be
|
|
|
|
// used to re-instantiate the Fs exactly so includes all the extra
|
|
|
|
// parameters passed in.
|
|
|
|
func ConfigStringFull(f Fs) string {
|
|
|
|
return configString(f, true)
|
|
|
|
}
|
|
|
|
|
2021-06-14 11:42:49 +00:00
|
|
|
// TemporaryLocalFs creates a local FS in the OS's temporary directory.
|
|
|
|
//
|
|
|
|
// No cleanup is performed, the caller must call Purge on the Fs themselves.
|
|
|
|
func TemporaryLocalFs(ctx context.Context) (Fs, error) {
|
2022-08-20 14:38:02 +00:00
|
|
|
path, err := os.MkdirTemp("", "rclone-spool")
|
2021-06-14 11:42:49 +00:00
|
|
|
if err == nil {
|
|
|
|
err = os.Remove(path)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
path = filepath.ToSlash(path)
|
|
|
|
return NewFs(ctx, path)
|
|
|
|
}
|