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 }