mirror of
https://github.com/rclone/rclone.git
synced 2024-12-23 07:29:35 +01:00
jottacloud: add support for upload to custom device and mountpoint
See #5926
This commit is contained in:
parent
700ca23a71
commit
01340acad2
@ -127,7 +127,7 @@ func init() {
|
||||
func Config(ctx context.Context, name string, m configmap.Mapper, config fs.ConfigIn) (*fs.ConfigOut, error) {
|
||||
switch config.State {
|
||||
case "":
|
||||
return fs.ConfigChooseExclusiveFixed("auth_type_done", "config_type", `Authentication type.`, []fs.OptionExample{{
|
||||
return fs.ConfigChooseExclusiveFixed("auth_type_done", "config_type", `Select authentication type.`, []fs.OptionExample{{
|
||||
Value: "standard",
|
||||
Help: "Standard authentication.\nUse this if you're a normal Jottacloud user.",
|
||||
}, {
|
||||
@ -145,7 +145,7 @@ func Config(ctx context.Context, name string, m configmap.Mapper, config fs.Conf
|
||||
return fs.ConfigGoto(config.Result)
|
||||
case "standard": // configure a jottacloud backend using the modern JottaCli token based authentication
|
||||
m.Set("configVersion", fmt.Sprint(configVersion))
|
||||
return fs.ConfigInput("standard_token", "config_login_token", "Personal login token.\n\nGenerate here: https://www.jottacloud.com/web/secure")
|
||||
return fs.ConfigInput("standard_token", "config_login_token", "Personal login token.\nGenerate here: https://www.jottacloud.com/web/secure")
|
||||
case "standard_token":
|
||||
loginToken := config.Result
|
||||
m.Set(configClientID, defaultClientID)
|
||||
@ -262,7 +262,11 @@ machines.`)
|
||||
},
|
||||
})
|
||||
case "choose_device":
|
||||
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?")
|
||||
return fs.ConfigConfirm("choose_device_query", false, "config_non_standard", `Use a non-standard device/mountpoint?
|
||||
Choosing no, the default, will let you access the storage used for the archive
|
||||
section of the official Jottacloud client. If you instead want to access the
|
||||
sync or the backup section, for example, you must choose yes.`)
|
||||
|
||||
case "choose_device_query":
|
||||
if config.Result != "true" {
|
||||
m.Set(configDevice, "")
|
||||
@ -286,12 +290,27 @@ machines.`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fs.ConfigChooseExclusive("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, ""
|
||||
|
||||
deviceNames := make([]string, len(acc.Devices))
|
||||
for i, dev := range acc.Devices {
|
||||
if i > 0 && dev.Name == defaultDevice {
|
||||
// Insert the special Jotta device as first entry, making it the default choice.
|
||||
copy(deviceNames[1:i+1], deviceNames[0:i])
|
||||
deviceNames[0] = dev.Name
|
||||
} else {
|
||||
deviceNames[i] = dev.Name
|
||||
}
|
||||
}
|
||||
|
||||
help := fmt.Sprintf(`The device to use. In standard setup the built-in %s device is used,
|
||||
which contains predefined mountpoints for archive, sync etc. All other devices
|
||||
are treated as backup devices by the official Jottacloud client. You may create
|
||||
a new by entering a unique name.`, defaultDevice)
|
||||
return fs.ConfigChoose("choose_device_result", "config_device", help, len(deviceNames), func(i int) (string, string) {
|
||||
return deviceNames[i], ""
|
||||
})
|
||||
case "choose_device_result":
|
||||
device := config.Result
|
||||
m.Set(configDevice, device)
|
||||
|
||||
oAuthClient, _, err := getOAuthClient(ctx, name, m)
|
||||
if err != nil {
|
||||
@ -300,16 +319,89 @@ machines.`)
|
||||
srv := rest.NewClient(oAuthClient).SetRoot(rootURL)
|
||||
|
||||
username, _ := m.Get(configUsername)
|
||||
dev, err := getDeviceInfo(ctx, srv, path.Join(username, device))
|
||||
|
||||
acc, err := getDriveInfo(ctx, srv, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fs.ConfigChooseExclusive("choose_device_mountpoint", "config_mountpoint", `Please select the mountpoint to use. Normally this will be Archive.`, len(dev.MountPoints), func(i int) (string, string) {
|
||||
isNew := true
|
||||
for _, dev := range acc.Devices {
|
||||
if strings.EqualFold(dev.Name, device) { // If device name exists with different casing we prefer the existing (not sure if and how the api handles the opposite)
|
||||
device = dev.Name // Prefer same casing as existing, e.g. if user entered "jotta" we use the standard casing "Jotta" instead
|
||||
isNew = false
|
||||
break
|
||||
}
|
||||
}
|
||||
var dev *api.JottaDevice
|
||||
if isNew {
|
||||
fs.Debugf(nil, "Creating new device: %s", device)
|
||||
dev, err = createDevice(ctx, srv, path.Join(username, device))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
m.Set(configDevice, device)
|
||||
|
||||
if !isNew {
|
||||
dev, err = getDeviceInfo(ctx, srv, path.Join(username, device))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var help string
|
||||
if device == defaultDevice {
|
||||
// With built-in Jotta device the mountpoint choice is exclusive,
|
||||
// we do not want to risk any problems by creating new mountpoints on it.
|
||||
help = fmt.Sprintf(`The mountpoint to use on the built-in device %s.
|
||||
The standard setup is to use the %s mountpoint. Most other mountpoints
|
||||
have very limited support in rclone and should generally be avoided.`, defaultDevice, defaultMountpoint)
|
||||
return fs.ConfigChooseExclusive("choose_device_mountpoint", "config_mountpoint", help, len(dev.MountPoints), func(i int) (string, string) {
|
||||
return dev.MountPoints[i].Name, ""
|
||||
})
|
||||
}
|
||||
help = fmt.Sprintf(`The mountpoint to use on the non-standard device %s.
|
||||
You may create a new by entering a unique name.`, device)
|
||||
return fs.ConfigChoose("choose_device_mountpoint", "config_mountpoint", help, len(dev.MountPoints), func(i int) (string, string) {
|
||||
return dev.MountPoints[i].Name, ""
|
||||
})
|
||||
case "choose_device_mountpoint":
|
||||
mountpoint := config.Result
|
||||
|
||||
oAuthClient, _, err := getOAuthClient(ctx, name, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
srv := rest.NewClient(oAuthClient).SetRoot(rootURL)
|
||||
|
||||
username, _ := m.Get(configUsername)
|
||||
device, _ := m.Get(configDevice)
|
||||
|
||||
dev, err := getDeviceInfo(ctx, srv, path.Join(username, device))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isNew := true
|
||||
for _, mnt := range dev.MountPoints {
|
||||
if strings.EqualFold(mnt.Name, mountpoint) {
|
||||
mountpoint = mnt.Name
|
||||
isNew = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if isNew {
|
||||
if device == defaultDevice {
|
||||
return nil, fmt.Errorf("custom mountpoints not supported on built-in %s device: %w", defaultDevice, err)
|
||||
}
|
||||
fs.Debugf(nil, "Creating new mountpoint: %s", mountpoint)
|
||||
_, err := createMountPoint(ctx, srv, path.Join(username, device, mountpoint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
m.Set(configMountpoint, mountpoint)
|
||||
|
||||
return fs.ConfigGoto("end")
|
||||
case "end":
|
||||
// All the config flows end up here in case we need to carry on with something
|
||||
@ -338,6 +430,7 @@ type Fs struct {
|
||||
opt Options
|
||||
features *fs.Features
|
||||
endpointURL string
|
||||
allocateURL string
|
||||
srv *rest.Client
|
||||
apiSrv *rest.Client
|
||||
pacer *fs.Pacer
|
||||
@ -588,6 +681,37 @@ func getDeviceInfo(ctx context.Context, srv *rest.Client, path string) (info *ap
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// createDevice makes a device
|
||||
func createDevice(ctx context.Context, srv *rest.Client, path string) (info *api.JottaDevice, err error) {
|
||||
opts := rest.Opts{
|
||||
Method: "POST",
|
||||
Path: urlPathEscape(path),
|
||||
Parameters: url.Values{},
|
||||
}
|
||||
|
||||
opts.Parameters.Set("type", "WORKSTATION")
|
||||
|
||||
_, err = srv.CallXML(ctx, &opts, nil, &info)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create device: %w", err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// createMountPoint makes a mount point
|
||||
func createMountPoint(ctx context.Context, srv *rest.Client, path string) (info *api.JottaMountPoint, err error) {
|
||||
opts := rest.Opts{
|
||||
Method: "POST",
|
||||
Path: urlPathEscape(path),
|
||||
}
|
||||
|
||||
_, err = srv.CallXML(ctx, &opts, nil, &info)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create mountpoint: %w", err)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// setEndpointURL generates the API endpoint URL
|
||||
func (f *Fs) setEndpointURL() {
|
||||
if f.opt.Device == "" {
|
||||
@ -597,6 +721,7 @@ func (f *Fs) setEndpointURL() {
|
||||
f.opt.Mountpoint = defaultMountpoint
|
||||
}
|
||||
f.endpointURL = path.Join(f.user, f.opt.Device, f.opt.Mountpoint)
|
||||
f.allocateURL = path.Join("/jfs", f.opt.Device, f.opt.Mountpoint)
|
||||
}
|
||||
|
||||
// readMetaDataForPath reads the metadata from the path
|
||||
@ -662,6 +787,11 @@ func (f *Fs) filePath(file string) string {
|
||||
return urlPathEscape(f.filePathRaw(file))
|
||||
}
|
||||
|
||||
// allocatePathRaw returns an unescaped file path (f.root, file)
|
||||
func (f *Fs) allocatePathRaw(file string) string {
|
||||
return path.Join(f.endpointURL, f.opt.Enc.FromStandardPath(path.Join(f.root, file)))
|
||||
}
|
||||
|
||||
// Jottacloud requires the grant_type 'refresh_token' string
|
||||
// to be uppercase and throws a 400 Bad Request if we use the
|
||||
// lower case used by the oauth2 module
|
||||
@ -1101,9 +1231,6 @@ func (f *Fs) createObject(remote string, modTime time.Time, size int64) (o *Obje
|
||||
//
|
||||
// The new object may have been created if an error is returned
|
||||
func (f *Fs) Put(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
|
||||
if f.opt.Device != "Jotta" {
|
||||
return nil, errors.New("upload not supported for devices other than Jotta")
|
||||
}
|
||||
o := f.createObject(src.Remote(), src.ModTime(ctx), src.Size())
|
||||
return o, o.Update(ctx, in, src, options...)
|
||||
}
|
||||
@ -1738,7 +1865,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
|
||||
Created: fileDate,
|
||||
Modified: fileDate,
|
||||
Md5: md5String,
|
||||
Path: path.Join(o.fs.opt.Mountpoint, o.fs.opt.Enc.FromStandardPath(path.Join(o.fs.root, o.remote))),
|
||||
Path: path.Join(o.fs.allocateURL, o.fs.opt.Enc.FromStandardPath(path.Join(o.fs.root, o.remote))),
|
||||
}
|
||||
|
||||
// send it
|
||||
|
@ -75,60 +75,83 @@ s) Set configuration password
|
||||
q) Quit config
|
||||
n/s/q> n
|
||||
name> remote
|
||||
Option Storage.
|
||||
Type of storage to configure.
|
||||
Enter a string value. Press Enter for the default ("").
|
||||
Choose a number from below, or type in your own value
|
||||
Choose a number from below, or type in your own value.
|
||||
[snip]
|
||||
XX / Jottacloud
|
||||
\ "jottacloud"
|
||||
\ (jottacloud)
|
||||
[snip]
|
||||
Storage> jottacloud
|
||||
** See help for jottacloud backend at: https://rclone.org/jottacloud/ **
|
||||
|
||||
Edit advanced config? (y/n)
|
||||
y) Yes
|
||||
n) No
|
||||
y/n> n
|
||||
Remote config
|
||||
Use legacy authentication?.
|
||||
This is only required for certain whitelabel versions of Jottacloud and not recommended for normal users.
|
||||
Edit advanced config?
|
||||
y) Yes
|
||||
n) No (default)
|
||||
y/n> n
|
||||
|
||||
Generate a personal login token here: https://www.jottacloud.com/web/secure
|
||||
Option config_type.
|
||||
Select authentication type.
|
||||
Choose a number from below, or type in an existing string value.
|
||||
Press Enter for the default (standard).
|
||||
/ Standard authentication.
|
||||
1 | Use this if you're a normal Jottacloud user.
|
||||
\ (standard)
|
||||
/ Legacy authentication.
|
||||
2 | This is only required for certain whitelabel versions of Jottacloud and not recommended for normal users.
|
||||
\ (legacy)
|
||||
/ Telia Cloud authentication.
|
||||
3 | Use this if you are using Telia Cloud.
|
||||
\ (telia)
|
||||
/ Tele2 Cloud authentication.
|
||||
4 | Use this if you are using Tele2 Cloud.
|
||||
\ (tele2)
|
||||
config_type> 1
|
||||
Personal login token.
|
||||
Generate here: https://www.jottacloud.com/web/secure
|
||||
Login Token> <your token here>
|
||||
|
||||
Do you want to use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?
|
||||
|
||||
Use a non-standard device/mountpoint?
|
||||
Choosing no, the default, will let you access the storage used for the archive
|
||||
section of the official Jottacloud client. If you instead want to access the
|
||||
sync or the backup section, for example, you must choose yes.
|
||||
y) Yes
|
||||
n) No
|
||||
n) No (default)
|
||||
y/n> y
|
||||
Please select the device to use. Normally this will be Jotta
|
||||
Choose a number from below, or type in an existing value
|
||||
Option config_device.
|
||||
The device to use. In standard setup the built-in Jotta device is used,
|
||||
which contains predefined mountpoints for archive, sync etc. All other devices
|
||||
are treated as backup devices by the official Jottacloud client. You may create
|
||||
a new by entering a unique name.
|
||||
Choose a number from below, or type in your own string value.
|
||||
Press Enter for the default (DESKTOP-3H31129).
|
||||
1 > DESKTOP-3H31129
|
||||
2 > Jotta
|
||||
Devices> 2
|
||||
Please select the mountpoint to user. Normally this will be Archive
|
||||
Choose a number from below, or type in an existing value
|
||||
config_device> 2
|
||||
Option config_mountpoint.
|
||||
The mountpoint to use for the built-in device Jotta.
|
||||
The standard setup is to use the Archive mountpoint. Most other mountpoints
|
||||
have very limited support in rclone and should generally be avoided.
|
||||
Choose a number from below, or type in an existing string value.
|
||||
Press Enter for the default (Archive).
|
||||
1 > Archive
|
||||
2 > Links
|
||||
2 > Shared
|
||||
3 > Sync
|
||||
|
||||
Mountpoints> 1
|
||||
config_mountpoint> 1
|
||||
--------------------
|
||||
[jotta]
|
||||
[remote]
|
||||
type = jottacloud
|
||||
configVersion = 1
|
||||
client_id = jottacli
|
||||
client_secret =
|
||||
tokenURL = https://id.jottacloud.com/auth/realms/jottacloud/protocol/openid-connect/token
|
||||
token = {........}
|
||||
username = 2940e57271a93d987d6f8a21
|
||||
device = Jotta
|
||||
mountpoint = Archive
|
||||
configVersion = 1
|
||||
--------------------
|
||||
y) Yes this is OK
|
||||
y) Yes this is OK (default)
|
||||
e) Edit this remote
|
||||
d) Delete this remote
|
||||
y/e/d> y
|
||||
```
|
||||
|
||||
Once configured you can then use `rclone` like this,
|
||||
|
||||
List directories in top level of your Jottacloud
|
||||
@ -145,19 +168,27 @@ To copy a local directory to an Jottacloud directory called backup
|
||||
|
||||
### Devices and Mountpoints
|
||||
|
||||
The official Jottacloud client registers a device for each computer you install it on,
|
||||
and then creates a mountpoint for each folder you select for Backup.
|
||||
The web interface uses a special device called Jotta for the Archive and Sync mountpoints.
|
||||
The official Jottacloud client registers a device for each computer you install
|
||||
it on, and shows them in the backup section of the user interface. For each
|
||||
folder you select for backup it will create a mountpoint within this device.
|
||||
A built-in device called Jotta is special, and contains mountpoints Archive,
|
||||
Sync and some others, used for corresponding features in official clients.
|
||||
|
||||
With rclone you'll want to use the Jotta/Archive device/mountpoint in most cases, however if you
|
||||
want to access files uploaded by any of the official clients rclone provides the option to select
|
||||
other devices and mountpoints during config. Note that uploading files is currently not supported
|
||||
to other devices than Jotta.
|
||||
With rclone you'll want to use the standard Jotta/Archive device/mountpoint in
|
||||
most cases. However, you may for example want to access files from the sync or
|
||||
backup functionality provided by the official clients, and rclone therefore
|
||||
provides the option to select other devices and mountpoints during config.
|
||||
|
||||
The built-in Jotta device may also contain several other mountpoints, such as: Latest, Links, Shared and Trash.
|
||||
These are special mountpoints with a different internal representation than the "regular" mountpoints.
|
||||
Rclone will only to a very limited degree support them. Generally you should avoid these, unless you know what you
|
||||
are doing.
|
||||
You are allowed to create new devices and mountpoints. All devices except the
|
||||
built-in Jotta device are treated as backup devices by official Jottacloud
|
||||
clients, and the mountpoints on them are individual backup sets.
|
||||
|
||||
With the built-in Jotta device, only existing, built-in, mountpoints can be
|
||||
selected. In addition to the mentioned Archive and Sync, it may contain
|
||||
several other mountpoints such as: Latest, Links, Shared and Trash. All of
|
||||
these are special mountpoints with a different internal representation than
|
||||
the "regular" mountpoints. Rclone will only to a very limited degree support
|
||||
them. Generally you should avoid these, unless you know what you are doing.
|
||||
|
||||
### --fast-list
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user