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

     1  // Textual user interface parts of the config system
     2  
     3  package config
     4  
     5  import (
     6  	"bufio"
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"log"
    11  	"os"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"unicode/utf8"
    16  
    17  	"github.com/rclone/rclone/fs"
    18  	"github.com/rclone/rclone/fs/config/configmap"
    19  	"github.com/rclone/rclone/fs/config/configstruct"
    20  	"github.com/rclone/rclone/fs/config/obscure"
    21  	"github.com/rclone/rclone/fs/driveletter"
    22  	"github.com/rclone/rclone/fs/fspath"
    23  	"github.com/rclone/rclone/lib/terminal"
    24  	"golang.org/x/text/unicode/norm"
    25  )
    26  
    27  // ReadLine reads some input
    28  var ReadLine = func() string {
    29  	buf := bufio.NewReader(os.Stdin)
    30  	line, err := buf.ReadString('\n')
    31  	if err != nil {
    32  		log.Fatalf("Failed to read line: %v", err)
    33  	}
    34  	return strings.TrimSpace(line)
    35  }
    36  
    37  // ReadNonEmptyLine prints prompt and calls Readline until non empty
    38  func ReadNonEmptyLine(prompt string) string {
    39  	result := ""
    40  	for result == "" {
    41  		fmt.Print(prompt)
    42  		result = strings.TrimSpace(ReadLine())
    43  	}
    44  	return result
    45  }
    46  
    47  // CommandDefault - choose one.  If return is pressed then it will
    48  // chose the defaultIndex if it is >= 0
    49  //
    50  // Must not call fs.Log anything from here to avoid deadlock in
    51  // --interactive --progress
    52  func CommandDefault(commands []string, defaultIndex int) byte {
    53  	opts := []string{}
    54  	for i, text := range commands {
    55  		def := ""
    56  		if i == defaultIndex {
    57  			def = " (default)"
    58  		}
    59  		fmt.Printf("%c) %s%s\n", text[0], text[1:], def)
    60  		opts = append(opts, text[:1])
    61  	}
    62  	optString := strings.Join(opts, "")
    63  	optHelp := strings.Join(opts, "/")
    64  	for {
    65  		fmt.Printf("%s> ", optHelp)
    66  		result := strings.ToLower(ReadLine())
    67  		if len(result) == 0 {
    68  			if defaultIndex >= 0 {
    69  				return optString[defaultIndex]
    70  			}
    71  			fmt.Printf("This value is required and it has no default.\n")
    72  		} else if len(result) == 1 {
    73  			i := strings.Index(optString, string(result[0]))
    74  			if i >= 0 {
    75  				return result[0]
    76  			}
    77  			fmt.Printf("This value must be one of the following characters: %s.\n", strings.Join(opts, ", "))
    78  		} else {
    79  			fmt.Printf("This value must be a single character, one of the following: %s.\n", strings.Join(opts, ", "))
    80  		}
    81  	}
    82  }
    83  
    84  // Command - choose one
    85  func Command(commands []string) byte {
    86  	return CommandDefault(commands, -1)
    87  }
    88  
    89  // Confirm asks the user for Yes or No and returns true or false
    90  //
    91  // If the user presses enter then the Default will be used
    92  func Confirm(Default bool) bool {
    93  	defaultIndex := 0
    94  	if !Default {
    95  		defaultIndex = 1
    96  	}
    97  	return CommandDefault([]string{"yYes", "nNo"}, defaultIndex) == 'y'
    98  }
    99  
   100  // Choose one of the choices, or default, or type a new string if newOk is set
   101  func Choose(what string, kind string, choices, help []string, defaultValue string, required bool, newOk bool) string {
   102  	valueDescription := "an existing"
   103  	if newOk {
   104  		valueDescription = "your own"
   105  	}
   106  	fmt.Printf("Choose a number from below, or type in %s %s.\n", valueDescription, kind)
   107  	// Empty input is allowed if not required is set, or if
   108  	// required is set but there is a default value to use.
   109  	if defaultValue != "" {
   110  		fmt.Printf("Press Enter for the default (%s).\n", defaultValue)
   111  	} else if !required {
   112  		fmt.Printf("Press Enter to leave empty.\n")
   113  	}
   114  	attributes := []string{terminal.HiRedFg, terminal.HiGreenFg}
   115  	for i, text := range choices {
   116  		var lines []string
   117  		if help != nil && help[i] != "" {
   118  			parts := strings.Split(help[i], "\n")
   119  			lines = append(lines, parts...)
   120  			lines = append(lines, fmt.Sprintf("(%s)", text))
   121  		}
   122  		pos := i + 1
   123  		terminal.WriteString(attributes[i%len(attributes)])
   124  		if len(lines) == 0 {
   125  			fmt.Printf("%2d > %s\n", pos, text)
   126  		} else {
   127  			mid := (len(lines) - 1) / 2
   128  			for i, line := range lines {
   129  				var sep rune
   130  				switch i {
   131  				case 0:
   132  					sep = '/'
   133  				case len(lines) - 1:
   134  					sep = '\\'
   135  				default:
   136  					sep = '|'
   137  				}
   138  				number := "  "
   139  				if i == mid {
   140  					number = fmt.Sprintf("%2d", pos)
   141  				}
   142  				fmt.Printf("%s %c %s\n", number, sep, line)
   143  			}
   144  		}
   145  		terminal.WriteString(terminal.Reset)
   146  	}
   147  	for {
   148  		fmt.Printf("%s> ", what)
   149  		result := ReadLine()
   150  		i, err := strconv.Atoi(result)
   151  		if err != nil {
   152  			for _, v := range choices {
   153  				if result == v {
   154  					return result
   155  				}
   156  			}
   157  			if result == "" {
   158  				// If empty string is in the predefined list of choices it has already been returned above.
   159  				// If parameter required is not set, then empty string is always a valid value.
   160  				if !required {
   161  					return result
   162  				}
   163  				// If parameter required is set, but there is a default, then empty input means default.
   164  				if defaultValue != "" {
   165  					return defaultValue
   166  				}
   167  				// If parameter required is set, and there is no default, then an input value is required.
   168  				fmt.Printf("This value is required and it has no default.\n")
   169  			} else if newOk {
   170  				// If legal input is not restricted to defined choices, then any nonzero input string is accepted.
   171  				return result
   172  			} else {
   173  				// A nonzero input string was specified but it did not match any of the strictly defined choices.
   174  				fmt.Printf("This value must match %s value.\n", valueDescription)
   175  			}
   176  		} else {
   177  			if i >= 1 && i <= len(choices) {
   178  				return choices[i-1]
   179  			}
   180  			fmt.Printf("No choices with this number.\n")
   181  		}
   182  	}
   183  }
   184  
   185  // Enter prompts for an input value of a specified type
   186  func Enter(what string, kind string, defaultValue string, required bool) string {
   187  	// Empty input is allowed if not required is set, or if
   188  	// required is set but there is a default value to use.
   189  	fmt.Printf("Enter a %s.", kind)
   190  	if defaultValue != "" {
   191  		fmt.Printf(" Press Enter for the default (%s).\n", defaultValue)
   192  	} else if !required {
   193  		fmt.Println(" Press Enter to leave empty.")
   194  	} else {
   195  		fmt.Println()
   196  	}
   197  	for {
   198  		fmt.Printf("%s> ", what)
   199  		result := ReadLine()
   200  		if !required || result != "" {
   201  			return result
   202  		}
   203  		if defaultValue != "" {
   204  			return defaultValue
   205  		}
   206  		fmt.Printf("This value is required and it has no default.\n")
   207  	}
   208  }
   209  
   210  // ChoosePassword asks the user for a password
   211  func ChoosePassword(defaultValue string, required bool) string {
   212  	fmt.Printf("Choose an alternative below.")
   213  	actions := []string{"yYes, type in my own password", "gGenerate random password"}
   214  	defaultAction := -1
   215  	if defaultValue != "" {
   216  		defaultAction = len(actions)
   217  		actions = append(actions, "nNo, keep existing")
   218  		fmt.Printf(" Press Enter for the default (%s).", string(actions[defaultAction][0]))
   219  	} else if !required {
   220  		defaultAction = len(actions)
   221  		actions = append(actions, "nNo, leave this optional password blank")
   222  		fmt.Printf(" Press Enter for the default (%s).", string(actions[defaultAction][0]))
   223  	}
   224  	fmt.Println()
   225  	var password string
   226  	var err error
   227  	switch i := CommandDefault(actions, defaultAction); i {
   228  	case 'y':
   229  		password = ChangePassword("the")
   230  	case 'g':
   231  		for {
   232  			fmt.Printf("Password strength in bits.\n64 is just about memorable\n128 is secure\n1024 is the maximum\n")
   233  			bits := ChooseNumber("Bits", 64, 1024)
   234  			password, err = Password(bits)
   235  			if err != nil {
   236  				log.Fatalf("Failed to make password: %v", err)
   237  			}
   238  			fmt.Printf("Your password is: %s\n", password)
   239  			fmt.Printf("Use this password? Please note that an obscured version of this \npassword (and not the " +
   240  				"password itself) will be stored under your \nconfiguration file, so keep this generated password " +
   241  				"in a safe place.\n")
   242  			if Confirm(true) {
   243  				break
   244  			}
   245  		}
   246  	case 'n':
   247  		return defaultValue
   248  	default:
   249  		fs.Errorf(nil, "Bad choice %c", i)
   250  	}
   251  	return obscure.MustObscure(password)
   252  }
   253  
   254  // ChooseNumber asks the user to enter a number between min and max
   255  // inclusive prompting them with what.
   256  func ChooseNumber(what string, min, max int) int {
   257  	for {
   258  		fmt.Printf("%s> ", what)
   259  		result := ReadLine()
   260  		i, err := strconv.Atoi(result)
   261  		if err != nil {
   262  			fmt.Printf("Bad number: %v\n", err)
   263  			continue
   264  		}
   265  		if i < min || i > max {
   266  			fmt.Printf("Out of range - %d to %d inclusive\n", min, max)
   267  			continue
   268  		}
   269  		return i
   270  	}
   271  }
   272  
   273  // ShowRemotes shows an overview of the config file
   274  func ShowRemotes() {
   275  	remotes := LoadedData().GetSectionList()
   276  	if len(remotes) == 0 {
   277  		return
   278  	}
   279  	sort.Strings(remotes)
   280  	fmt.Printf("%-20s %s\n", "Name", "Type")
   281  	fmt.Printf("%-20s %s\n", "====", "====")
   282  	for _, remote := range remotes {
   283  		fmt.Printf("%-20s %s\n", remote, FileGet(remote, "type"))
   284  	}
   285  }
   286  
   287  // ChooseRemote chooses a remote name
   288  func ChooseRemote() string {
   289  	remotes := LoadedData().GetSectionList()
   290  	sort.Strings(remotes)
   291  	fmt.Println("Select remote.")
   292  	return Choose("remote", "value", remotes, nil, "", true, false)
   293  }
   294  
   295  // mustFindByName finds the RegInfo for the remote name passed in or
   296  // exits with a fatal error.
   297  func mustFindByName(name string) *fs.RegInfo {
   298  	fsType := FileGet(name, "type")
   299  	if fsType == "" {
   300  		log.Fatalf("Couldn't find type of fs for %q", name)
   301  	}
   302  	return fs.MustFind(fsType)
   303  }
   304  
   305  // findByName finds the RegInfo for the remote name passed in or
   306  // returns an error
   307  func findByName(name string) (*fs.RegInfo, error) {
   308  	fsType := FileGet(name, "type")
   309  	if fsType == "" {
   310  		return nil, fmt.Errorf("couldn't find type of fs for %q", name)
   311  	}
   312  	return fs.Find(fsType)
   313  }
   314  
   315  // printRemoteOptions prints the options of the remote
   316  func printRemoteOptions(name string, prefix string, sep string, redacted bool) {
   317  	fsInfo, err := findByName(name)
   318  	if err != nil {
   319  		fmt.Printf("# %v\n", err)
   320  		fsInfo = nil
   321  	}
   322  	for _, key := range LoadedData().GetKeyList(name) {
   323  		isPassword := false
   324  		isSensitive := false
   325  		if fsInfo != nil {
   326  			for _, option := range fsInfo.Options {
   327  				if option.Name == key {
   328  					if option.IsPassword {
   329  						isPassword = true
   330  					} else if option.Sensitive {
   331  						isSensitive = true
   332  					}
   333  				}
   334  			}
   335  		}
   336  		value := FileGet(name, key)
   337  		if redacted && (isSensitive || isPassword) && value != "" {
   338  			fmt.Printf("%s%s%sXXX\n", prefix, key, sep)
   339  		} else if isPassword && value != "" {
   340  			fmt.Printf("%s%s%s*** ENCRYPTED ***\n", prefix, key, sep)
   341  		} else {
   342  			fmt.Printf("%s%s%s%s\n", prefix, key, sep, value)
   343  		}
   344  	}
   345  }
   346  
   347  // listRemoteOptions lists the options of the remote
   348  func listRemoteOptions(name string) {
   349  	printRemoteOptions(name, "- ", ": ", false)
   350  }
   351  
   352  // ShowRemote shows the contents of the remote in config file format
   353  func ShowRemote(name string) {
   354  	fmt.Printf("[%s]\n", name)
   355  	printRemoteOptions(name, "", " = ", false)
   356  }
   357  
   358  // ShowRedactedRemote shows the contents of the remote in config file format
   359  func ShowRedactedRemote(name string) {
   360  	fmt.Printf("[%s]\n", name)
   361  	printRemoteOptions(name, "", " = ", true)
   362  }
   363  
   364  // OkRemote prints the contents of the remote and ask if it is OK
   365  func OkRemote(name string) bool {
   366  	fmt.Println("Configuration complete.")
   367  	fmt.Println("Options:")
   368  	listRemoteOptions(name)
   369  	fmt.Printf("Keep this %q remote?\n", name)
   370  	switch i := CommandDefault([]string{"yYes this is OK", "eEdit this remote", "dDelete this remote"}, 0); i {
   371  	case 'y':
   372  		return true
   373  	case 'e':
   374  		return false
   375  	case 'd':
   376  		LoadedData().DeleteSection(name)
   377  		return true
   378  	default:
   379  		fs.Errorf(nil, "Bad choice %c", i)
   380  	}
   381  	return false
   382  }
   383  
   384  // newSection prints an empty line to separate sections
   385  func newSection() {
   386  	fmt.Println()
   387  }
   388  
   389  // backendConfig configures the backend starting from the state passed in
   390  //
   391  // The is the user interface loop that drives the post configuration backend config.
   392  func backendConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs.RegInfo, choices configmap.Getter, startState string) error {
   393  	in := fs.ConfigIn{
   394  		State: startState,
   395  	}
   396  	for {
   397  		out, err := fs.BackendConfig(ctx, name, m, ri, choices, in)
   398  		if err != nil {
   399  			return err
   400  		}
   401  		if out == nil {
   402  			break
   403  		}
   404  		if out.Error != "" {
   405  			fmt.Println(out.Error)
   406  		}
   407  		in.State = out.State
   408  		in.Result = out.Result
   409  		if out.Option != nil {
   410  			fs.Debugf(name, "config: reading config parameter %q", out.Option.Name)
   411  			if out.Option.Default == nil {
   412  				out.Option.Default = ""
   413  			}
   414  			if Default, isBool := out.Option.Default.(bool); isBool &&
   415  				len(out.Option.Examples) == 2 &&
   416  				out.Option.Examples[0].Help == "Yes" &&
   417  				out.Option.Examples[0].Value == "true" &&
   418  				out.Option.Examples[1].Help == "No" &&
   419  				out.Option.Examples[1].Value == "false" &&
   420  				out.Option.Exclusive {
   421  				// Use Confirm for Yes/No questions as it has a nicer interface=
   422  				fmt.Println(out.Option.Help)
   423  				in.Result = fmt.Sprint(Confirm(Default))
   424  			} else {
   425  				value := ChooseOption(out.Option, name)
   426  				if value != "" {
   427  					err := out.Option.Set(value)
   428  					if err != nil {
   429  						return fmt.Errorf("failed to set option: %w", err)
   430  					}
   431  				}
   432  				in.Result = out.Option.String()
   433  			}
   434  		}
   435  		if out.State == "" {
   436  			break
   437  		}
   438  		newSection()
   439  	}
   440  	return nil
   441  }
   442  
   443  // PostConfig configures the backend after the main config has been done
   444  //
   445  // The is the user interface loop that drives the post configuration backend config.
   446  func PostConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs.RegInfo) error {
   447  	if ri.Config == nil {
   448  		return errors.New("backend doesn't support reconnect or authorize")
   449  	}
   450  	return backendConfig(ctx, name, m, ri, configmap.Simple{}, "")
   451  }
   452  
   453  // RemoteConfig runs the config helper for the remote if needed
   454  func RemoteConfig(ctx context.Context, name string) error {
   455  	fmt.Printf("Remote config\n")
   456  	ri := mustFindByName(name)
   457  	m := fs.ConfigMap(ri, name, nil)
   458  	if ri.Config == nil {
   459  		return nil
   460  	}
   461  	return PostConfig(ctx, name, m, ri)
   462  }
   463  
   464  // ChooseOption asks the user to choose an option
   465  func ChooseOption(o *fs.Option, name string) string {
   466  	fmt.Printf("Option %s.\n", o.Name)
   467  	if o.Help != "" {
   468  		// Show help string without empty lines.
   469  		help := strings.ReplaceAll(strings.TrimSpace(o.Help), "\n\n", "\n")
   470  		fmt.Println(help)
   471  	}
   472  
   473  	var defaultValue string
   474  	if o.Default == nil {
   475  		defaultValue = ""
   476  	} else {
   477  		defaultValue = fmt.Sprint(o.Default)
   478  	}
   479  
   480  	if o.IsPassword {
   481  		return ChoosePassword(defaultValue, o.Required)
   482  	}
   483  
   484  	what := "value"
   485  	if o.Default != "" {
   486  		switch o.Default.(type) {
   487  		case bool:
   488  			what = "boolean value (true or false)"
   489  		case fs.SizeSuffix:
   490  			what = "size with suffix K,M,G,T"
   491  		case fs.Duration:
   492  			what = "duration s,m,h,d,w,M,y"
   493  		case int, int8, int16, int32, int64:
   494  			what = "signed integer"
   495  		case uint, byte, uint16, uint32, uint64:
   496  			what = "unsigned integer"
   497  		default:
   498  			what = fmt.Sprintf("value of type %s", o.Type())
   499  		}
   500  	}
   501  	var in string
   502  	for {
   503  		if len(o.Examples) > 0 {
   504  			var values []string
   505  			var help []string
   506  			for _, example := range o.Examples {
   507  				values = append(values, example.Value)
   508  				help = append(help, example.Help)
   509  			}
   510  			in = Choose(o.Name, what, values, help, defaultValue, o.Required, !o.Exclusive)
   511  		} else {
   512  			in = Enter(o.Name, what, defaultValue, o.Required)
   513  		}
   514  		if in != "" {
   515  			newIn, err := configstruct.StringToInterface(o.Default, in)
   516  			if err != nil {
   517  				fmt.Printf("Failed to parse %q: %v\n", in, err)
   518  				continue
   519  			}
   520  			in = fmt.Sprint(newIn) // canonicalise
   521  		}
   522  		return in
   523  	}
   524  }
   525  
   526  // NewRemoteName asks the user for a name for a new remote
   527  func NewRemoteName() (name string) {
   528  	for {
   529  		fmt.Println("Enter name for new remote.")
   530  		fmt.Printf("name> ")
   531  		name = ReadLine()
   532  		if LoadedData().HasSection(name) {
   533  			fmt.Printf("Remote %q already exists.\n", name)
   534  			continue
   535  		}
   536  		err := fspath.CheckConfigName(name)
   537  		switch {
   538  		case name == "":
   539  			fmt.Printf("Can't use empty name.\n")
   540  		case driveletter.IsDriveLetter(name):
   541  			fmt.Printf("Can't use %q as it can be confused with a drive letter.\n", name)
   542  		case err != nil:
   543  			fmt.Printf("Can't use %q as %v.\n", name, err)
   544  		default:
   545  			return name
   546  		}
   547  	}
   548  }
   549  
   550  // NewRemote make a new remote from its name
   551  func NewRemote(ctx context.Context, name string) error {
   552  	var (
   553  		newType string
   554  		ri      *fs.RegInfo
   555  		err     error
   556  	)
   557  
   558  	// Set the type first
   559  	for {
   560  		newType = ChooseOption(fsOption(), name)
   561  		ri, err = fs.Find(newType)
   562  		if err != nil {
   563  			fmt.Printf("Bad remote %q: %v\n", newType, err)
   564  			continue
   565  		}
   566  		break
   567  	}
   568  	LoadedData().SetValue(name, "type", newType)
   569  	newSection()
   570  	_, err = CreateRemote(ctx, name, newType, nil, UpdateRemoteOpt{
   571  		All: true,
   572  	})
   573  	if err != nil {
   574  		return err
   575  	}
   576  	if OkRemote(name) {
   577  		SaveConfig()
   578  		return nil
   579  	}
   580  	newSection()
   581  	return EditRemote(ctx, ri, name)
   582  }
   583  
   584  // EditRemote gets the user to edit a remote
   585  func EditRemote(ctx context.Context, ri *fs.RegInfo, name string) error {
   586  	fmt.Printf("Editing existing %q remote with options:\n", name)
   587  	listRemoteOptions(name)
   588  	newSection()
   589  	for {
   590  		_, err := UpdateRemote(ctx, name, nil, UpdateRemoteOpt{
   591  			All: true,
   592  		})
   593  		if err != nil {
   594  			return err
   595  		}
   596  		if OkRemote(name) {
   597  			break
   598  		}
   599  	}
   600  	SaveConfig()
   601  	return nil
   602  }
   603  
   604  // DeleteRemote gets the user to delete a remote
   605  func DeleteRemote(name string) {
   606  	LoadedData().DeleteSection(name)
   607  	SaveConfig()
   608  }
   609  
   610  // copyRemote asks the user for a new remote name and copies name into
   611  // it. Returns the new name.
   612  func copyRemote(name string) string {
   613  	newName := NewRemoteName()
   614  	// Copy the keys
   615  	for _, key := range LoadedData().GetKeyList(name) {
   616  		value := getWithDefault(name, key, "")
   617  		LoadedData().SetValue(newName, key, value)
   618  	}
   619  	return newName
   620  }
   621  
   622  // RenameRemote renames a config section
   623  func RenameRemote(name string) {
   624  	fmt.Printf("Enter new name for %q remote.\n", name)
   625  	newName := copyRemote(name)
   626  	if name != newName {
   627  		LoadedData().DeleteSection(name)
   628  		SaveConfig()
   629  	}
   630  }
   631  
   632  // CopyRemote copies a config section
   633  func CopyRemote(name string) {
   634  	fmt.Printf("Enter name for copy of %q remote.\n", name)
   635  	copyRemote(name)
   636  	SaveConfig()
   637  }
   638  
   639  // ShowConfigLocation prints the location of the config file in use
   640  func ShowConfigLocation() {
   641  	if configPath := GetConfigPath(); configPath == "" {
   642  		fmt.Println("Configuration is in memory only")
   643  	} else {
   644  		if _, err := os.Stat(configPath); os.IsNotExist(err) {
   645  			fmt.Println("Configuration file doesn't exist, but rclone will use this path:")
   646  		} else {
   647  			fmt.Println("Configuration file is stored at:")
   648  		}
   649  		fmt.Printf("%s\n", configPath)
   650  	}
   651  }
   652  
   653  // ShowConfig prints the (unencrypted) config options
   654  func ShowConfig() {
   655  	str, err := LoadedData().Serialize()
   656  	if err != nil {
   657  		log.Fatalf("Failed to serialize config: %v", err)
   658  	}
   659  	if str == "" {
   660  		str = "; empty config\n"
   661  	}
   662  	fmt.Printf("%s", str)
   663  }
   664  
   665  // ShowRedactedConfig prints the redacted (unencrypted) config options
   666  func ShowRedactedConfig() {
   667  	remotes := LoadedData().GetSectionList()
   668  	if len(remotes) == 0 {
   669  		fmt.Println("; empty config")
   670  		return
   671  	}
   672  	sort.Strings(remotes)
   673  	for i, remote := range remotes {
   674  		if i != 0 {
   675  			fmt.Println()
   676  		}
   677  		ShowRedactedRemote(remote)
   678  	}
   679  }
   680  
   681  // EditConfig edits the config file interactively
   682  func EditConfig(ctx context.Context) (err error) {
   683  	for {
   684  		haveRemotes := len(LoadedData().GetSectionList()) != 0
   685  		what := []string{"eEdit existing remote", "nNew remote", "dDelete remote", "rRename remote", "cCopy remote", "sSet configuration password", "qQuit config"}
   686  		if haveRemotes {
   687  			fmt.Printf("Current remotes:\n\n")
   688  			ShowRemotes()
   689  			fmt.Printf("\n")
   690  		} else {
   691  			fmt.Printf("No remotes found, make a new one?\n")
   692  			// take 2nd item and last 2 items of menu list
   693  			what = append(what[1:2], what[len(what)-2:]...)
   694  		}
   695  		switch i := Command(what); i {
   696  		case 'e':
   697  			newSection()
   698  			name := ChooseRemote()
   699  			newSection()
   700  			fs := mustFindByName(name)
   701  			err = EditRemote(ctx, fs, name)
   702  			if err != nil {
   703  				return err
   704  			}
   705  		case 'n':
   706  			newSection()
   707  			name := NewRemoteName()
   708  			newSection()
   709  			err = NewRemote(ctx, name)
   710  			if err != nil {
   711  				return err
   712  			}
   713  		case 'd':
   714  			newSection()
   715  			name := ChooseRemote()
   716  			newSection()
   717  			DeleteRemote(name)
   718  		case 'r':
   719  			newSection()
   720  			name := ChooseRemote()
   721  			newSection()
   722  			RenameRemote(name)
   723  		case 'c':
   724  			newSection()
   725  			name := ChooseRemote()
   726  			newSection()
   727  			CopyRemote(name)
   728  		case 's':
   729  			newSection()
   730  			SetPassword()
   731  		case 'q':
   732  			return nil
   733  		}
   734  		newSection()
   735  	}
   736  }
   737  
   738  // Suppress the confirm prompts by altering the context config
   739  func suppressConfirm(ctx context.Context) context.Context {
   740  	newCtx, ci := fs.AddConfig(ctx)
   741  	ci.AutoConfirm = true
   742  	return newCtx
   743  }
   744  
   745  // checkPassword normalises and validates the password
   746  func checkPassword(password string) (string, error) {
   747  	if !utf8.ValidString(password) {
   748  		return "", errors.New("password contains invalid utf8 characters")
   749  	}
   750  	// Check for leading/trailing whitespace
   751  	trimmedPassword := strings.TrimSpace(password)
   752  	// Warn user if password has leading+trailing whitespace
   753  	if len(password) != len(trimmedPassword) {
   754  		_, _ = fmt.Fprintln(os.Stderr, "Your password contains leading/trailing whitespace - in previous versions of rclone this was stripped")
   755  	}
   756  	// Normalize to reduce weird variations.
   757  	password = norm.NFKC.String(password)
   758  	if len(password) == 0 || len(trimmedPassword) == 0 {
   759  		return "", errors.New("no characters in password")
   760  	}
   761  	return password, nil
   762  }
   763  
   764  // GetPassword asks the user for a password with the prompt given.
   765  func GetPassword(prompt string) string {
   766  	_, _ = fmt.Fprintln(PasswordPromptOutput, prompt)
   767  	for {
   768  		_, _ = fmt.Fprint(PasswordPromptOutput, "password:")
   769  		password := ReadPassword()
   770  		password, err := checkPassword(password)
   771  		if err == nil {
   772  			return password
   773  		}
   774  		_, _ = fmt.Fprintf(os.Stderr, "Bad password: %v\n", err)
   775  	}
   776  }
   777  
   778  // ChangePassword will query the user twice for the named password. If
   779  // the same password is entered it is returned.
   780  func ChangePassword(name string) string {
   781  	for {
   782  		a := GetPassword(fmt.Sprintf("Enter %s password:", name))
   783  		b := GetPassword(fmt.Sprintf("Confirm %s password:", name))
   784  		if a == b {
   785  			return a
   786  		}
   787  		fmt.Println("Passwords do not match!")
   788  	}
   789  }
   790  
   791  // SetPassword will allow the user to modify the current
   792  // configuration encryption settings.
   793  func SetPassword() {
   794  	for {
   795  		if len(configKey) > 0 {
   796  			fmt.Println("Your configuration is encrypted.")
   797  			what := []string{"cChange Password", "uUnencrypt configuration", "qQuit to main menu"}
   798  			switch i := Command(what); i {
   799  			case 'c':
   800  				changeConfigPassword()
   801  				SaveConfig()
   802  				fmt.Println("Password changed")
   803  				continue
   804  			case 'u':
   805  				configKey = nil
   806  				SaveConfig()
   807  				continue
   808  			case 'q':
   809  				return
   810  			}
   811  
   812  		} else {
   813  			fmt.Println("Your configuration is not encrypted.")
   814  			fmt.Println("If you add a password, you will protect your login information to cloud services.")
   815  			what := []string{"aAdd Password", "qQuit to main menu"}
   816  			switch i := Command(what); i {
   817  			case 'a':
   818  				changeConfigPassword()
   819  				SaveConfig()
   820  				fmt.Println("Password set")
   821  				continue
   822  			case 'q':
   823  				return
   824  			}
   825  		}
   826  	}
   827  }