fs: add names to each config parameter so we can override them #3455

This commit is contained in:
Nick Craig-Wood 2021-05-04 12:27:50 +01:00
parent 94dbfa4ea6
commit f122808d86
10 changed files with 96 additions and 66 deletions

View File

@ -208,9 +208,9 @@ func init() {
return fs.ConfigGoto("teamdrive") return fs.ConfigGoto("teamdrive")
case "teamdrive": case "teamdrive":
if opt.TeamDriveID == "" { if opt.TeamDriveID == "" {
return fs.ConfigConfirm("teamdrive_ok", false, "Configure this as a Shared Drive (Team Drive)?\n") return fs.ConfigConfirm("teamdrive_ok", false, "config_change_team_drive", "Configure this as a Shared Drive (Team Drive)?\n")
} }
return fs.ConfigConfirm("teamdrive_ok", false, fmt.Sprintf("Change current Shared Drive (Team Drive) ID %q?\n", opt.TeamDriveID)) return fs.ConfigConfirm("teamdrive_ok", false, "config_change_team_drive", fmt.Sprintf("Change current Shared Drive (Team Drive) ID %q?\n", opt.TeamDriveID))
case "teamdrive_ok": case "teamdrive_ok":
if config.Result == "false" { if config.Result == "false" {
m.Set("team_drive", "") m.Set("team_drive", "")
@ -227,7 +227,7 @@ func init() {
if len(teamDrives) == 0 { if len(teamDrives) == 0 {
return fs.ConfigError("", "No Shared Drives found in your account") return fs.ConfigError("", "No Shared Drives found in your account")
} }
return fs.ConfigChoose("teamdrive_final", "Shared Drive", len(teamDrives), func(i int) (string, string) { return fs.ConfigChoose("teamdrive_final", "config_team_drive", "Shared Drive", len(teamDrives), func(i int) (string, string) {
teamDrive := teamDrives[i] teamDrive := teamDrives[i]
return teamDrive.Id, teamDrive.Name return teamDrive.Id, teamDrive.Name
}) })

View File

@ -98,7 +98,7 @@ func init() {
}) })
case "warning": case "warning":
// Warn the user as required by google photos integration // Warn the user as required by google photos integration
return fs.ConfigConfirm("warning_done", true, `Warning return fs.ConfigConfirm("warning_done", true, "config_warning", `Warning
IMPORTANT: All media items uploaded to Google Photos with rclone IMPORTANT: All media items uploaded to Google Photos with rclone
are stored in full resolution at original quality. These uploads are stored in full resolution at original quality. These uploads

View File

@ -126,7 +126,7 @@ func init() {
func Config(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) { func Config(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) {
switch config.State { switch config.State {
case "": case "":
return fs.ConfigChooseFixed("auth_type_done", `Authentication type`, []fs.OptionExample{{ return fs.ConfigChooseFixed("auth_type_done", "config_type", `Authentication type`, []fs.OptionExample{{
Value: "standard", Value: "standard",
Help: "Standard authentication - use this if you're a normal Jottacloud user.", Help: "Standard authentication - use this if you're a normal Jottacloud user.",
}, { }, {
@ -141,7 +141,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
return fs.ConfigGoto(config.Result) return fs.ConfigGoto(config.Result)
case "standard": // configure a jottacloud backend using the modern JottaCli token based authentication case "standard": // configure a jottacloud backend using the modern JottaCli token based authentication
m.Set("configVersion", fmt.Sprint(configVersion)) m.Set("configVersion", fmt.Sprint(configVersion))
return fs.ConfigInput("standard_token", "Personal login token.\n\nGenerate here: https://www.jottacloud.com/web/secure") return fs.ConfigInput("standard_token", "config_login_token", "Personal login token.\n\nGenerate here: https://www.jottacloud.com/web/secure")
case "standard_token": case "standard_token":
loginToken := config.Result loginToken := config.Result
m.Set(configClientID, "jottacli") m.Set(configClientID, "jottacli")
@ -159,7 +159,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
return fs.ConfigGoto("choose_device") return fs.ConfigGoto("choose_device")
case "legacy": // configure a jottacloud backend using legacy authentication case "legacy": // configure a jottacloud backend using legacy authentication
m.Set("configVersion", fmt.Sprint(v1configVersion)) m.Set("configVersion", fmt.Sprint(v1configVersion))
return fs.ConfigConfirm("legacy_api", false, `Do you want to create a machine specific API key? return fs.ConfigConfirm("legacy_api", false, "config_machine_specific", `Do you want to create a machine specific API key?
Rclone has it's own Jottacloud API KEY which works fine as long as one Rclone has it's own Jottacloud API KEY which works fine as long as one
only uses rclone on a single machine. When you want to use rclone with only uses rclone on a single machine. When you want to use rclone with
@ -177,10 +177,10 @@ machines.`)
m.Set(configClientSecret, obscure.MustObscure(deviceRegistration.ClientSecret)) m.Set(configClientSecret, obscure.MustObscure(deviceRegistration.ClientSecret))
fs.Debugf(nil, "Got clientID %q and clientSecret %q", deviceRegistration.ClientID, deviceRegistration.ClientSecret) fs.Debugf(nil, "Got clientID %q and clientSecret %q", deviceRegistration.ClientID, deviceRegistration.ClientSecret)
} }
return fs.ConfigInput("legacy_user", "Username") return fs.ConfigInput("legacy_user", "config_user", "Username")
case "legacy_username": case "legacy_username":
m.Set(configUsername, config.Result) m.Set(configUsername, config.Result)
return fs.ConfigPassword("legacy_password", "Jottacloud password\n\n(this is only required during setup and will not be stored).") return fs.ConfigPassword("legacy_password", "config_password", "Jottacloud password\n\n(this is only required during setup and will not be stored).")
case "legacy_password": case "legacy_password":
m.Set("password", config.Result) m.Set("password", config.Result)
m.Set("auth_code", "") m.Set("auth_code", "")
@ -213,7 +213,7 @@ machines.`)
token, err := doAuthV1(ctx, srv, username, password, authCode) token, err := doAuthV1(ctx, srv, username, password, authCode)
if err == errAuthCodeRequired { if err == errAuthCodeRequired {
return fs.ConfigInput("legacy_auth_code", "Verification Code\nThis account uses 2 factor authentication you will receive a verification code via SMS.") return fs.ConfigInput("legacy_auth_code", "config_auth_code", "Verification Code\nThis account uses 2 factor authentication you will receive a verification code via SMS.")
} }
m.Set("password", "") m.Set("password", "")
m.Set("auth_code", "") m.Set("auth_code", "")
@ -241,7 +241,7 @@ machines.`)
}, },
}) })
case "choose_device": case "choose_device":
return fs.ConfigConfirm("choose_device_query", false, "Use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?") return fs.ConfigConfirm("choose_device_query", false, "config_non_standard", "Use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?")
case "choose_device_query": case "choose_device_query":
if config.Result != "true" { if config.Result != "true" {
m.Set(configDevice, "") m.Set(configDevice, "")
@ -265,7 +265,7 @@ machines.`)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return fs.ConfigChoose("choose_device_result", `Please select the device to use. Normally this will be Jotta`, len(acc.Devices), func(i int) (string, string) { return fs.ConfigChoose("choose_device_result", "config_device", `Please select the device to use. Normally this will be Jotta`, len(acc.Devices), func(i int) (string, string) {
return acc.Devices[i].Name, "" return acc.Devices[i].Name, ""
}) })
case "choose_device_result": case "choose_device_result":
@ -283,7 +283,7 @@ machines.`)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return fs.ConfigChoose("choose_device_mountpoint", `Please select the mountpoint to use. Normally this will be Archive.`, len(dev.MountPoints), func(i int) (string, string) { return fs.ConfigChoose("choose_device_mountpoint", "config_mountpoint", `Please select the mountpoint to use. Normally this will be Archive.`, len(dev.MountPoints), func(i int) (string, string) {
return dev.MountPoints[i].Name, "" return dev.MountPoints[i].Name, ""
}) })
case "choose_device_mountpoint": case "choose_device_mountpoint":

View File

@ -363,7 +363,7 @@ func chooseDrive(ctx context.Context, name string, m configmap.Mapper, srv *rest
if len(drives.Drives) == 0 { if len(drives.Drives) == 0 {
return fs.ConfigError("choose_type", "No drives found") return fs.ConfigError("choose_type", "No drives found")
} }
return fs.ConfigChoose("driveid_final", "Select drive you want to use", len(drives.Drives), func(i int) (string, string) { return fs.ConfigChoose("driveid_final", "config_driveid", "Select drive you want to use", len(drives.Drives), func(i int) (string, string) {
drive := drives.Drives[i] drive := drives.Drives[i]
return drive.DriveID, fmt.Sprintf("%s (%s)", drive.DriveName, drive.DriveType) return drive.DriveID, fmt.Sprintf("%s (%s)", drive.DriveName, drive.DriveType)
}) })
@ -388,7 +388,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
OAuth2Config: oauthConfig, OAuth2Config: oauthConfig,
}) })
case "choose_type": case "choose_type":
return fs.ConfigChooseFixed("choose_type_done", "Type of connection", []fs.OptionExample{{ return fs.ConfigChooseFixed("choose_type_done", "config_type", "Type of connection", []fs.OptionExample{{
Value: "onedrive", Value: "onedrive",
Help: "OneDrive Personal or Business", Help: "OneDrive Personal or Business",
}, { }, {
@ -430,19 +430,19 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
}, },
}) })
case "driveid": case "driveid":
return fs.ConfigInput("driveid_end", "Drive ID") return fs.ConfigInput("driveid_end", "config_driveid_fixed", "Drive ID")
case "driveid_end": case "driveid_end":
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{ return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
finalDriveID: config.Result, finalDriveID: config.Result,
}) })
case "siteid": case "siteid":
return fs.ConfigInput("siteid_end", "Site ID") return fs.ConfigInput("siteid_end", "config_siteid", "Site ID")
case "siteid_end": case "siteid_end":
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{ return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
siteID: config.Result, siteID: config.Result,
}) })
case "url": case "url":
return fs.ConfigInput("url_end", `Site URL return fs.ConfigInput("url_end", "config_site_url", `Site URL
Example: "https://contoso.sharepoint.com/sites/mysite" or "mysite" Example: "https://contoso.sharepoint.com/sites/mysite" or "mysite"
`) `)
@ -459,13 +459,13 @@ Example: "https://contoso.sharepoint.com/sites/mysite" or "mysite"
relativePath: "/sites/" + siteURL, relativePath: "/sites/" + siteURL,
}) })
case "path": case "path":
return fs.ConfigInput("path_end", `Server-relative URL`) return fs.ConfigInput("path_end", "config_sharepoint_url", `Server-relative URL`)
case "path_end": case "path_end":
return chooseDrive(ctx, name, m, srv, chooseDriveOpt{ return chooseDrive(ctx, name, m, srv, chooseDriveOpt{
relativePath: config.Result, relativePath: config.Result,
}) })
case "search": case "search":
return fs.ConfigInput("search_end", `Search term`) return fs.ConfigInput("search_end", "config_search_term", `Search term`)
case "search_end": case "search_end":
searchTerm := config.Result searchTerm := config.Result
opts := rest.Opts{ opts := rest.Opts{
@ -483,7 +483,7 @@ Example: "https://contoso.sharepoint.com/sites/mysite" or "mysite"
if len(sites.Sites) == 0 { if len(sites.Sites) == 0 {
return fs.ConfigError("choose_type", fmt.Sprintf("search for %q returned no results", searchTerm)) return fs.ConfigError("choose_type", fmt.Sprintf("search for %q returned no results", searchTerm))
} }
return fs.ConfigChoose("search_sites", `Select the Site you want to use`, len(sites.Sites), func(i int) (string, string) { return fs.ConfigChoose("search_sites", "config_site", `Select the Site you want to use`, len(sites.Sites), func(i int) (string, string) {
site := sites.Sites[i] site := sites.Sites[i]
return site.SiteID, fmt.Sprintf("%s (%s)", site.SiteName, site.SiteURL) return site.SiteID, fmt.Sprintf("%s (%s)", site.SiteName, site.SiteURL)
}) })
@ -508,7 +508,7 @@ Example: "https://contoso.sharepoint.com/sites/mysite" or "mysite"
m.Set(configDriveID, finalDriveID) m.Set(configDriveID, finalDriveID)
m.Set(configDriveType, rootItem.ParentReference.DriveType) m.Set(configDriveType, rootItem.ParentReference.DriveType)
return fs.ConfigConfirm("driveid_final_end", true, fmt.Sprintf("Drive OK?\n\nFound drive %q of type %q\nURL: %s\n", rootItem.Name, rootItem.ParentReference.DriveType, rootItem.WebURL)) return fs.ConfigConfirm("driveid_final_end", true, "config_drive_ok", fmt.Sprintf("Drive OK?\n\nFound drive %q of type %q\nURL: %s\n", rootItem.Name, rootItem.ParentReference.DriveType, rootItem.WebURL))
case "driveid_final_end": case "driveid_final_end":
if config.Result == "true" { if config.Result == "true" {
return nil, nil return nil, nil

View File

@ -327,7 +327,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
case "": case "":
// Just make sure we do have a password // Just make sure we do have a password
if password == "" { if password == "" {
return fs.ConfigPassword("", "Two-factor authentication: please enter your password (it won't be saved in the configuration)") return fs.ConfigPassword("", "config_password", "Two-factor authentication: please enter your password (it won't be saved in the configuration)")
} }
return fs.ConfigGoto("password") return fs.ConfigGoto("password")
case "password": case "password":
@ -338,7 +338,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
m.Set(configPassword, obscure.MustObscure(config.Result)) m.Set(configPassword, obscure.MustObscure(config.Result))
return fs.ConfigGoto("2fa") return fs.ConfigGoto("2fa")
case "2fa": case "2fa":
return fs.ConfigInput("2fa_do", "Two-factor authentication: please enter your 2FA code") return fs.ConfigInput("2fa_do", "config_2fa", "Two-factor authentication: please enter your 2FA code")
case "2fa_do": case "2fa_do":
code := config.Result code := config.Result
if code == "" { if code == "" {
@ -358,10 +358,10 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
token, err := getAuthorizationToken(ctx, srv, username, password, code) token, err := getAuthorizationToken(ctx, srv, username, password, code)
if err != nil { if err != nil {
return fs.ConfigConfirm("2fa_error", true, fmt.Sprintf("Authentication failed: %v\n\nTry Again?", err)) return fs.ConfigConfirm("2fa_error", true, "config_retry", fmt.Sprintf("Authentication failed: %v\n\nTry Again?", err))
} }
if token == "" { if token == "" {
return fs.ConfigConfirm("2fa_error", true, "Authentication failed - no token returned.\n\nTry Again?") return fs.ConfigConfirm("2fa_error", true, "config_retry", "Authentication failed - no token returned.\n\nTry Again?")
} }
// Let's save the token into the configuration // Let's save the token into the configuration
m.Set(configAuthToken, token) m.Set(configAuthToken, token)

View File

@ -87,17 +87,17 @@ func init() {
if opt.RefreshToken == "" { if opt.RefreshToken == "" {
return fs.ConfigGoto("username") return fs.ConfigGoto("username")
} }
return fs.ConfigConfirm("refresh", true, "Already have a token - refresh?") return fs.ConfigConfirm("refresh", true, "config_refresh", "Already have a token - refresh?")
case "refresh": case "refresh":
if config.Result == "false" { if config.Result == "false" {
return nil, nil return nil, nil
} }
return fs.ConfigGoto("username") return fs.ConfigGoto("username")
case "username": case "username":
return fs.ConfigInput("password", "username (email address)") return fs.ConfigInput("password", "config_username", "username (email address)")
case "password": case "password":
m.Set("username", config.Result) m.Set("username", config.Result)
return fs.ConfigPassword("auth", "Your Sugarsync password.\n\nOnly required during setup and will not be stored.") return fs.ConfigPassword("auth", "config_password", "Your Sugarsync password.\n\nOnly required during setup and will not be stored.")
case "auth": case "auth":
username, _ := m.Get("username") username, _ := m.Get("username")
m.Set("username", "") m.Set("username", "")

View File

@ -131,7 +131,7 @@ func init() {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return fs.ConfigChoose("workspace", "Team Drive ID", len(teams), func(i int) (string, string) { return fs.ConfigChoose("workspace", "config_team_drive_id", "Team Drive ID", len(teams), func(i int) (string, string) {
team := teams[i] team := teams[i]
return team.ID, team.Attributes.Name return team.ID, team.Attributes.Name
}) })
@ -145,7 +145,7 @@ func init() {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return fs.ConfigChoose("workspace_end", "Workspace ID", len(workspaces), func(i int) (string, string) { return fs.ConfigChoose("workspace_end", "config_workspace", "Workspace ID", len(workspaces), func(i int) (string, string) {
workspace := workspaces[i] workspace := workspaces[i]
return workspace.ID, workspace.Attributes.Name return workspace.ID, workspace.Attributes.Name
}) })

View File

@ -25,7 +25,9 @@ var ConfigOAuth func(ctx context.Context, name string, m configmap.Mapper, ri *R
// ConfigIn is passed to the Config function for an Fs // ConfigIn is passed to the Config function for an Fs
// //
// The interactive config system for backends is state based. This is so that different frontends to the config can be attached, eg over the API or web page. // The interactive config system for backends is state based. This is
// so that different frontends to the config can be attached, eg over
// the API or web page.
// //
// Each call to the config system supplies ConfigIn which tells the // Each call to the config system supplies ConfigIn which tells the
// system what to do. Each will return a ConfigOut which gives a // system what to do. Each will return a ConfigOut which gives a
@ -47,6 +49,10 @@ var ConfigOAuth func(ctx context.Context, name string, m configmap.Mapper, ri *R
// //
// The utilities here are convenience methods for different kinds of // The utilities here are convenience methods for different kinds of
// questions and responses. // questions and responses.
//
// Where the questions ask for a name then this should start with
// "config_" to show it is an ephemeral config input rather than the
// actual value stored in the config file.
type ConfigIn struct { type ConfigIn struct {
State string // State to run State string // State to run
Result string // Result from previous Option Result string // Result from previous Option
@ -66,33 +72,42 @@ type ConfigOut struct {
Result string // if Option/OAuth not set then this is passed to the next state Result string // if Option/OAuth not set then this is passed to the next state
} }
// ConfigInput asks the user for a string // ConfigInputOptional asks the user for a string which may be empty
// //
// state should be the next state required // state should be the next state required
// name is the config name for this item
// help should be the help shown to the user // help should be the help shown to the user
func ConfigInput(state string, help string) (*ConfigOut, error) { func ConfigInputOptional(state string, name string, help string) (*ConfigOut, error) {
return &ConfigOut{ return &ConfigOut{
State: state, State: state,
Option: &Option{ Option: &Option{
Name: name,
Help: help, Help: help,
Default: "", Default: "",
}, },
}, nil }, nil
} }
// ConfigInput asks the user for a non-empty string
//
// state should be the next state required
// name is the config name for this item
// help should be the help shown to the user
func ConfigInput(state string, name string, help string) (*ConfigOut, error) {
out, _ := ConfigInputOptional(state, name, help)
out.Option.Required = true
return out, nil
}
// ConfigPassword asks the user for a password // ConfigPassword asks the user for a password
// //
// state should be the next state required // state should be the next state required
// name is the config name for this item
// help should be the help shown to the user // help should be the help shown to the user
func ConfigPassword(state string, help string) (*ConfigOut, error) { func ConfigPassword(state string, name string, help string) (*ConfigOut, error) {
return &ConfigOut{ out, _ := ConfigInputOptional(state, name, help)
State: state, out.Option.IsPassword = true
Option: &Option{ return out, nil
Help: help,
Default: "",
IsPassword: true,
},
}, nil
} }
// ConfigGoto goes to the next state with empty Result // ConfigGoto goes to the next state with empty Result
@ -130,11 +145,13 @@ func ConfigError(state string, Error string) (*ConfigOut, error) {
// //
// state should be the next state required // state should be the next state required
// Default should be the default state // Default should be the default state
// name is the config name for this item
// help should be the help shown to the user // help should be the help shown to the user
func ConfigConfirm(state string, Default bool, help string) (*ConfigOut, error) { func ConfigConfirm(state string, Default bool, name string, help string) (*ConfigOut, error) {
return &ConfigOut{ return &ConfigOut{
State: state, State: state,
Option: &Option{ Option: &Option{
Name: name,
Help: help, Help: help,
Default: Default, Default: Default,
Examples: []OptionExample{{ Examples: []OptionExample{{
@ -151,19 +168,21 @@ func ConfigConfirm(state string, Default bool, help string) (*ConfigOut, error)
// ConfigChooseFixed returns a ConfigOut structure which has a list of items to choose from. // ConfigChooseFixed returns a ConfigOut structure which has a list of items to choose from.
// //
// state should be the next state required // state should be the next state required
// name is the config name for this item
// help should be the help shown to the user // help should be the help shown to the user
// items should be the items in the list // items should be the items in the list
// //
// It chooses the first item to be the default. // It chooses the first item to be the default.
// If there are no items then it will return an error. // If there are no items then it will return an error.
// If there is only one item it will short cut to the next state // If there is only one item it will short cut to the next state
func ConfigChooseFixed(state string, help string, items []OptionExample) (*ConfigOut, error) { func ConfigChooseFixed(state string, name string, help string, items []OptionExample) (*ConfigOut, error) {
if len(items) == 0 { if len(items) == 0 {
return nil, errors.Errorf("no items found in: %s", help) return nil, errors.Errorf("no items found in: %s", help)
} }
choose := &ConfigOut{ choose := &ConfigOut{
State: state, State: state,
Option: &Option{ Option: &Option{
Name: name,
Help: help, Help: help,
Examples: items, Examples: items,
}, },
@ -180,6 +199,7 @@ func ConfigChooseFixed(state string, help string, items []OptionExample) (*Confi
// ConfigChoose returns a ConfigOut structure which has a list of items to choose from. // ConfigChoose returns a ConfigOut structure which has a list of items to choose from.
// //
// state should be the next state required // state should be the next state required
// name is the config name for this item
// help should be the help shown to the user // help should be the help shown to the user
// n should be the number of items in the list // n should be the number of items in the list
// getItem should return the items (value, help) // getItem should return the items (value, help)
@ -187,12 +207,12 @@ func ConfigChooseFixed(state string, help string, items []OptionExample) (*Confi
// It chooses the first item to be the default. // It chooses the first item to be the default.
// If there are no items then it will return an error. // If there are no items then it will return an error.
// If there is only one item it will short cut to the next state // If there is only one item it will short cut to the next state
func ConfigChoose(state string, help string, n int, getItem func(i int) (itemValue string, itemHelp string)) (*ConfigOut, error) { func ConfigChoose(state string, name string, help string, n int, getItem func(i int) (itemValue string, itemHelp string)) (*ConfigOut, error) {
items := make(OptionExamples, n) items := make(OptionExamples, n)
for i := range items { for i := range items {
items[i].Value, items[i].Help = getItem(i) items[i].Value, items[i].Help = getItem(i)
} }
return ConfigChooseFixed(state, help, items) return ConfigChooseFixed(state, name, help, items)
} }
// StatePush pushes a new values onto the front of the config string // StatePush pushes a new values onto the front of the config string
@ -237,21 +257,21 @@ func StatePop(state string) (newState string, value string) {
// BackendConfig calls the config for the backend in ri // BackendConfig calls the config for the backend in ri
// //
// It wraps any OAuth transactions as necessary so only straight forward config questions are emitted // It wraps any OAuth transactions as necessary so only straight forward config questions are emitted
func BackendConfig(ctx context.Context, name string, m configmap.Mapper, ri *RegInfo, in ConfigIn) (*ConfigOut, error) { func BackendConfig(ctx context.Context, name string, m configmap.Mapper, ri *RegInfo, in ConfigIn) (out *ConfigOut, err error) {
ci := GetConfig(ctx) ci := GetConfig(ctx)
if ri.Config == nil { if ri.Config == nil {
return nil, nil return nil, nil
} }
// Do internal states here switch {
if strings.HasPrefix(in.State, "*") { case strings.HasPrefix(in.State, "*oauth"):
switch { // Do internal oauth states
case strings.HasPrefix(in.State, "*oauth"): out, err = ConfigOAuth(ctx, name, m, ri, in)
return ConfigOAuth(ctx, name, m, ri, in) case strings.HasPrefix(in.State, "*"):
default: err = errors.Errorf("unknown internal state %q", in.State)
return nil, errors.Errorf("unknown internal state %q", in.State) default:
} // Otherwise pass to backend
out, err = ri.Config(ctx, name, m, in)
} }
out, err := ri.Config(ctx, name, m, in)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -267,11 +287,21 @@ func BackendConfig(ctx context.Context, name string, m configmap.Mapper, ri *Reg
} }
// Run internal state, saving the input so we can recall the state // Run internal state, saving the input so we can recall the state
return ConfigGoto(StatePush("", "*oauth", returnState, in.State, in.Result)) return ConfigGoto(StatePush("", "*oauth", returnState, in.State, in.Result))
case out.Option != nil && ci.AutoConfirm: case out.Option != nil:
if out.Option.Name == "" {
return nil, errors.New("internal error: no name set in Option")
}
// If override value is set in the config then use that
if result, ok := m.Get(out.Option.Name); ok {
Debugf(nil, "Override value found, choosing value %q for state %q", result, out.State)
return ConfigResult(out.State, result)
}
// If AutoConfirm is set, choose the default value // If AutoConfirm is set, choose the default value
result := fmt.Sprint(out.Option.Default) if ci.AutoConfirm {
Debugf(nil, "Auto confirm is set, choosing default %q for state %q", result, out.State) result := fmt.Sprint(out.Option.Default)
return ConfigResult(out.State, result) Debugf(nil, "Auto confirm is set, choosing default %q for state %q", result, out.State)
return ConfigResult(out.State, result)
}
} }
return out, nil return out, nil
} }

View File

@ -245,7 +245,6 @@ func OkRemote(name string) bool {
// //
// The is the user interface loop that drives the post configuration backend config. // The is the user interface loop that drives the post configuration backend config.
func PostConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs.RegInfo) error { func PostConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs.RegInfo) error {
// FIXME if doing authorize, stop when we've got to the OAuth
if ri.Config == nil { if ri.Config == nil {
return errors.New("backend doesn't support reconnect or authorize") return errors.New("backend doesn't support reconnect or authorize")
} }
@ -267,6 +266,7 @@ func PostConfig(ctx context.Context, name string, m configmap.Mapper, ri *fs.Reg
in.State = out.State in.State = out.State
in.Result = out.Result in.Result = out.Result
if out.Option != nil { if out.Option != nil {
fs.Debugf(name, "config: reading config item named %q", out.Option.Name)
if out.Option.Default == nil { if out.Option.Default == nil {
out.Option.Default = "" out.Option.Default = ""
} }

View File

@ -455,14 +455,14 @@ func ConfigOAuth(ctx context.Context, name string, m configmap.Mapper, ri *fs.Re
// See if already have a token // See if already have a token
tokenString, ok := m.Get("token") tokenString, ok := m.Get("token")
if ok && tokenString != "" { if ok && tokenString != "" {
return fs.ConfigConfirm(newState("*oauth-confirm"), true, "Already have a token - refresh?") return fs.ConfigConfirm(newState("*oauth-confirm"), true, "config_refresh_token", "Already have a token - refresh?")
} }
return fs.ConfigGoto(newState("*oauth-confirm")) return fs.ConfigGoto(newState("*oauth-confirm"))
case "*oauth-confirm": case "*oauth-confirm":
if in.Result == "false" { if in.Result == "false" {
return fs.ConfigGoto(newState("*oauth-done")) return fs.ConfigGoto(newState("*oauth-done"))
} }
return fs.ConfigConfirm(newState("*oauth-islocal"), true, "Use auto config?\n * Say Y if not sure\n * Say N if you are working on a remote or headless machine\n") return fs.ConfigConfirm(newState("*oauth-islocal"), true, "config_is_local", "Use auto config?\n * Say Y if not sure\n * Say N if you are working on a remote or headless machine\n")
case "*oauth-islocal": case "*oauth-islocal":
if in.Result == "true" { if in.Result == "true" {
return fs.ConfigGoto(newState("*oauth-do")) return fs.ConfigGoto(newState("*oauth-do"))
@ -478,7 +478,7 @@ func ConfigOAuth(ctx context.Context, name string, m configmap.Mapper, ri *fs.Re
if err != nil { if err != nil {
return nil, err return nil, err
} }
return fs.ConfigInput(newState("*oauth-do"), fmt.Sprintf("Verification code\n\nGo to this URL, authenticate then paste the code here.\n\n%s\n", authURL)) return fs.ConfigInput(newState("*oauth-do"), "config_verification_code", fmt.Sprintf("Verification code\n\nGo to this URL, authenticate then paste the code here.\n\n%s\n", authURL))
} }
var out strings.Builder var out strings.Builder
fmt.Fprintf(&out, `For this to work, you will need rclone available on a machine that has fmt.Fprintf(&out, `For this to work, you will need rclone available on a machine that has
@ -508,7 +508,7 @@ version recommended):
fmt.Fprintf(&out, "\trclone authorize %q\n", ri.Name) fmt.Fprintf(&out, "\trclone authorize %q\n", ri.Name)
} }
fmt.Fprintln(&out, "\nThen paste the result.") fmt.Fprintln(&out, "\nThen paste the result.")
return fs.ConfigInput(newState("*oauth-authorize"), out.String()) return fs.ConfigInput(newState("*oauth-authorize"), "config_token", out.String())
case "*oauth-authorize": case "*oauth-authorize":
// Read the updates to the config // Read the updates to the config
outM := configmap.Simple{} outM := configmap.Simple{}