github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/registry.go (about)

     1  // Filesystem registry and backend options
     2  
     3  package fs
     4  
     5  import (
     6  	"context"
     7  	"encoding/json"
     8  	"fmt"
     9  	"log"
    10  	"reflect"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/rclone/rclone/fs/config/configmap"
    16  	"github.com/rclone/rclone/fs/config/configstruct"
    17  )
    18  
    19  // Registry of filesystems
    20  var Registry []*RegInfo
    21  
    22  // optDescription is a basic description option
    23  var optDescription = Option{
    24  	Name:     "description",
    25  	Help:     "Description of the remote.",
    26  	Advanced: true,
    27  }
    28  
    29  // RegInfo provides information about a filesystem
    30  type RegInfo struct {
    31  	// Name of this fs
    32  	Name string
    33  	// Description of this fs - defaults to Name
    34  	Description string
    35  	// Prefix for command line flags for this fs - defaults to Name if not set
    36  	Prefix string
    37  	// Create a new file system.  If root refers to an existing
    38  	// object, then it should return an Fs which points to
    39  	// the parent of that object and ErrorIsFile.
    40  	NewFs func(ctx context.Context, name string, root string, config configmap.Mapper) (Fs, error) `json:"-"`
    41  	// Function to call to help with config - see docs for ConfigIn for more info
    42  	Config func(ctx context.Context, name string, m configmap.Mapper, configIn ConfigIn) (*ConfigOut, error) `json:"-"`
    43  	// Options for the Fs configuration
    44  	Options Options
    45  	// The command help, if any
    46  	CommandHelp []CommandHelp
    47  	// Aliases - other names this backend is known by
    48  	Aliases []string
    49  	// Hide - if set don't show in the configurator
    50  	Hide bool
    51  	// MetadataInfo help about the metadata in use in this backend
    52  	MetadataInfo *MetadataInfo
    53  }
    54  
    55  // FileName returns the on disk file name for this backend
    56  func (ri *RegInfo) FileName() string {
    57  	return strings.ReplaceAll(ri.Name, " ", "")
    58  }
    59  
    60  // Options is a slice of configuration Option for a backend
    61  type Options []Option
    62  
    63  // Set the default values for the options
    64  func (os Options) setValues() {
    65  	for i := range os {
    66  		o := &os[i]
    67  		if o.Default == nil {
    68  			o.Default = ""
    69  		}
    70  		// Create options for Enums
    71  		if do, ok := o.Default.(Choices); ok && len(o.Examples) == 0 {
    72  			o.Exclusive = true
    73  			o.Required = true
    74  			o.Examples = make(OptionExamples, len(do.Choices()))
    75  			for i, choice := range do.Choices() {
    76  				o.Examples[i].Value = choice
    77  			}
    78  		}
    79  	}
    80  }
    81  
    82  // Get the Option corresponding to name or return nil if not found
    83  func (os Options) Get(name string) *Option {
    84  	for i := range os {
    85  		opt := &os[i]
    86  		if opt.Name == name {
    87  			return opt
    88  		}
    89  	}
    90  	return nil
    91  }
    92  
    93  // Overridden discovers which config items have been overridden in the
    94  // configmap passed in, either by the config string, command line
    95  // flags or environment variables
    96  func (os Options) Overridden(m *configmap.Map) configmap.Simple {
    97  	var overridden = configmap.Simple{}
    98  	for i := range os {
    99  		opt := &os[i]
   100  		value, isSet := m.GetPriority(opt.Name, configmap.PriorityNormal)
   101  		if isSet {
   102  			overridden.Set(opt.Name, value)
   103  		}
   104  	}
   105  	return overridden
   106  }
   107  
   108  // NonDefault discovers which config values aren't at their default
   109  func (os Options) NonDefault(m configmap.Getter) configmap.Simple {
   110  	var nonDefault = configmap.Simple{}
   111  	for i := range os {
   112  		opt := &os[i]
   113  		value, isSet := m.Get(opt.Name)
   114  		if !isSet {
   115  			continue
   116  		}
   117  		defaultValue := fmt.Sprint(opt.Default)
   118  		if value != defaultValue {
   119  			nonDefault.Set(opt.Name, value)
   120  		}
   121  	}
   122  	return nonDefault
   123  }
   124  
   125  // HasAdvanced discovers if any options have an Advanced setting
   126  func (os Options) HasAdvanced() bool {
   127  	for i := range os {
   128  		opt := &os[i]
   129  		if opt.Advanced {
   130  			return true
   131  		}
   132  	}
   133  	return false
   134  }
   135  
   136  // OptionVisibility controls whether the options are visible in the
   137  // configurator or the command line.
   138  type OptionVisibility byte
   139  
   140  // Constants Option.Hide
   141  const (
   142  	OptionHideCommandLine OptionVisibility = 1 << iota
   143  	OptionHideConfigurator
   144  	OptionHideBoth = OptionHideCommandLine | OptionHideConfigurator
   145  )
   146  
   147  // Option is describes an option for the config wizard
   148  //
   149  // This also describes command line options and environment variables.
   150  //
   151  // To create a multiple-choice option, specify the possible values
   152  // in the Examples property. Whether the option's value is required
   153  // to be one of these depends on other properties:
   154  //   - Default is to allow any value, either from specified examples,
   155  //     or any other value. To restrict exclusively to the specified
   156  //     examples, also set Exclusive=true.
   157  //   - If empty string should not be allowed then set Required=true,
   158  //     and do not set Default.
   159  type Option struct {
   160  	Name       string           // name of the option in snake_case
   161  	Help       string           // help, start with a single sentence on a single line that will be extracted for command line help
   162  	Provider   string           // set to filter on provider
   163  	Default    interface{}      // default value, nil => "", if set (and not to nil or "") then Required does nothing
   164  	Value      interface{}      // value to be set by flags
   165  	Examples   OptionExamples   `json:",omitempty"` // predefined values that can be selected from list (multiple-choice option)
   166  	ShortOpt   string           // the short option for this if required
   167  	Hide       OptionVisibility // set this to hide the config from the configurator or the command line
   168  	Required   bool             // this option is required, meaning value cannot be empty unless there is a default
   169  	IsPassword bool             // set if the option is a password
   170  	NoPrefix   bool             // set if the option for this should not use the backend prefix
   171  	Advanced   bool             // set if this is an advanced config option
   172  	Exclusive  bool             // set if the answer can only be one of the examples (empty string allowed unless Required or Default is set)
   173  	Sensitive  bool             // set if this option should be redacted when using rclone config redacted
   174  }
   175  
   176  // BaseOption is an alias for Option used internally
   177  type BaseOption Option
   178  
   179  // MarshalJSON turns an Option into JSON
   180  //
   181  // It adds some generated fields for ease of use
   182  // - DefaultStr - a string rendering of Default
   183  // - ValueStr - a string rendering of Value
   184  // - Type - the type of the option
   185  func (o *Option) MarshalJSON() ([]byte, error) {
   186  	return json.Marshal(struct {
   187  		BaseOption
   188  		DefaultStr string
   189  		ValueStr   string
   190  		Type       string
   191  	}{
   192  		BaseOption: BaseOption(*o),
   193  		DefaultStr: fmt.Sprint(o.Default),
   194  		ValueStr:   o.String(),
   195  		Type:       o.Type(),
   196  	})
   197  }
   198  
   199  // GetValue gets the current value which is the default if not set
   200  func (o *Option) GetValue() interface{} {
   201  	val := o.Value
   202  	if val == nil {
   203  		val = o.Default
   204  		if val == nil {
   205  			val = ""
   206  		}
   207  	}
   208  	return val
   209  }
   210  
   211  // String turns Option into a string
   212  func (o *Option) String() string {
   213  	return fmt.Sprint(o.GetValue())
   214  }
   215  
   216  // Set an Option from a string
   217  func (o *Option) Set(s string) (err error) {
   218  	newValue, err := configstruct.StringToInterface(o.GetValue(), s)
   219  	if err != nil {
   220  		return err
   221  	}
   222  	o.Value = newValue
   223  	return nil
   224  }
   225  
   226  type typer interface {
   227  	Type() string
   228  }
   229  
   230  // Type of the value
   231  func (o *Option) Type() string {
   232  	v := o.GetValue()
   233  
   234  	// Try to call Type method on non-pointer
   235  	if do, ok := v.(typer); ok {
   236  		return do.Type()
   237  	}
   238  
   239  	return reflect.TypeOf(v).Name()
   240  }
   241  
   242  // FlagName for the option
   243  func (o *Option) FlagName(prefix string) string {
   244  	name := strings.ReplaceAll(o.Name, "_", "-") // convert snake_case to kebab-case
   245  	if !o.NoPrefix {
   246  		name = prefix + "-" + name
   247  	}
   248  	return name
   249  }
   250  
   251  // EnvVarName for the option
   252  func (o *Option) EnvVarName(prefix string) string {
   253  	return OptionToEnv(prefix + "-" + o.Name)
   254  }
   255  
   256  // Copy makes a shallow copy of the option
   257  func (o *Option) Copy() *Option {
   258  	copy := new(Option)
   259  	*copy = *o
   260  	return copy
   261  }
   262  
   263  // OptionExamples is a slice of examples
   264  type OptionExamples []OptionExample
   265  
   266  // Len is part of sort.Interface.
   267  func (os OptionExamples) Len() int { return len(os) }
   268  
   269  // Swap is part of sort.Interface.
   270  func (os OptionExamples) Swap(i, j int) { os[i], os[j] = os[j], os[i] }
   271  
   272  // Less is part of sort.Interface.
   273  func (os OptionExamples) Less(i, j int) bool { return os[i].Help < os[j].Help }
   274  
   275  // Sort sorts an OptionExamples
   276  func (os OptionExamples) Sort() { sort.Sort(os) }
   277  
   278  // OptionExample describes an example for an Option
   279  type OptionExample struct {
   280  	Value    string
   281  	Help     string
   282  	Provider string
   283  }
   284  
   285  // Register a filesystem
   286  //
   287  // Fs modules  should use this in an init() function
   288  func Register(info *RegInfo) {
   289  	info.Options.setValues()
   290  	if info.Prefix == "" {
   291  		info.Prefix = info.Name
   292  	}
   293  	info.Options = append(info.Options, optDescription)
   294  	Registry = append(Registry, info)
   295  	for _, alias := range info.Aliases {
   296  		// Copy the info block and rename and hide the alias and options
   297  		aliasInfo := *info
   298  		aliasInfo.Name = alias
   299  		aliasInfo.Prefix = alias
   300  		aliasInfo.Hide = true
   301  		aliasInfo.Options = append(Options(nil), info.Options...)
   302  		for i := range aliasInfo.Options {
   303  			aliasInfo.Options[i].Hide = OptionHideBoth
   304  		}
   305  		Registry = append(Registry, &aliasInfo)
   306  	}
   307  }
   308  
   309  // Find looks for a RegInfo object for the name passed in.  The name
   310  // can be either the Name or the Prefix.
   311  //
   312  // Services are looked up in the config file
   313  func Find(name string) (*RegInfo, error) {
   314  	for _, item := range Registry {
   315  		if item.Name == name || item.Prefix == name || item.FileName() == name {
   316  			return item, nil
   317  		}
   318  	}
   319  	return nil, fmt.Errorf("didn't find backend called %q", name)
   320  }
   321  
   322  // MustFind looks for an Info object for the type name passed in
   323  //
   324  // Services are looked up in the config file.
   325  //
   326  // Exits with a fatal error if not found
   327  func MustFind(name string) *RegInfo {
   328  	fs, err := Find(name)
   329  	if err != nil {
   330  		log.Fatalf("Failed to find remote: %v", err)
   331  	}
   332  	return fs
   333  }
   334  
   335  // Type returns a textual string to identify the type of the remote
   336  func Type(f Fs) string {
   337  	typeName := fmt.Sprintf("%T", f)
   338  	typeName = strings.TrimPrefix(typeName, "*")
   339  	typeName = strings.TrimSuffix(typeName, ".Fs")
   340  	return typeName
   341  }
   342  
   343  var (
   344  	typeToRegInfoMu sync.Mutex
   345  	typeToRegInfo   = map[string]*RegInfo{}
   346  )
   347  
   348  // Add the RegInfo to the reverse map
   349  func addReverse(f Fs, fsInfo *RegInfo) {
   350  	typeToRegInfoMu.Lock()
   351  	defer typeToRegInfoMu.Unlock()
   352  	typeToRegInfo[Type(f)] = fsInfo
   353  }
   354  
   355  // FindFromFs finds the *RegInfo used to create this Fs, provided
   356  // it was created by fs.NewFs or cache.Get
   357  //
   358  // It returns nil if not found
   359  func FindFromFs(f Fs) *RegInfo {
   360  	typeToRegInfoMu.Lock()
   361  	defer typeToRegInfoMu.Unlock()
   362  	return typeToRegInfo[Type(f)]
   363  }