mirror of
https://github.com/rclone/rclone.git
synced 2025-12-12 14:23:24 +00:00
Compare commits
2 Commits
feat/cache
...
jotta_moun
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
854d083d42 | ||
|
|
424d0d0ac2 |
@@ -41,7 +41,7 @@ const (
|
|||||||
maxSleep = 2 * time.Second
|
maxSleep = 2 * time.Second
|
||||||
decayConstant = 2 // bigger for slower decay, exponential
|
decayConstant = 2 // bigger for slower decay, exponential
|
||||||
defaultDevice = "Jotta"
|
defaultDevice = "Jotta"
|
||||||
defaultMountpoint = "Sync" // nolint
|
defaultMountpoint = "Archive"
|
||||||
rootURL = "https://www.jottacloud.com/jfs/"
|
rootURL = "https://www.jottacloud.com/jfs/"
|
||||||
apiURL = "https://api.jottacloud.com/files/v1/"
|
apiURL = "https://api.jottacloud.com/files/v1/"
|
||||||
baseURL = "https://www.jottacloud.com/"
|
baseURL = "https://www.jottacloud.com/"
|
||||||
@@ -53,6 +53,8 @@ const (
|
|||||||
configUsername = "user"
|
configUsername = "user"
|
||||||
configClientID = "client_id"
|
configClientID = "client_id"
|
||||||
configClientSecret = "client_secret"
|
configClientSecret = "client_secret"
|
||||||
|
configDevice = "device"
|
||||||
|
configMountpoint = "mountpoint"
|
||||||
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,6 +71,7 @@ var (
|
|||||||
|
|
||||||
// Register with Fs
|
// Register with Fs
|
||||||
func init() {
|
func init() {
|
||||||
|
// needs to be done early so we can use oauth during config
|
||||||
fs.Register(&fs.RegInfo{
|
fs.Register(&fs.RegInfo{
|
||||||
Name: "jottacloud",
|
Name: "jottacloud",
|
||||||
Description: "JottaCloud",
|
Description: "JottaCloud",
|
||||||
@@ -135,7 +138,7 @@ func init() {
|
|||||||
if !ok {
|
if !ok {
|
||||||
log.Fatalf("No username defined")
|
log.Fatalf("No username defined")
|
||||||
}
|
}
|
||||||
password := config.GetPassword("Your Jottacloud password is only required during config and will not be stored.")
|
password := config.GetPassword("Your Jottacloud password is only required during setup and will not be stored.")
|
||||||
|
|
||||||
// prepare out token request with username and password
|
// prepare out token request with username and password
|
||||||
values := url.Values{}
|
values := url.Values{}
|
||||||
@@ -157,7 +160,7 @@ func init() {
|
|||||||
// if 2fa is enabled the first request is expected to fail. We will do another request with the 2fa code as an additional http header
|
// if 2fa is enabled the first request is expected to fail. We will do another request with the 2fa code as an additional http header
|
||||||
if resp != nil {
|
if resp != nil {
|
||||||
if resp.Header.Get("X-JottaCloud-OTP") == "required; SMS" {
|
if resp.Header.Get("X-JottaCloud-OTP") == "required; SMS" {
|
||||||
fmt.Printf("This account has 2 factor authentication enabled you will receive a verification code via SMS.\n")
|
fmt.Printf("This account uses 2 factor authentication you will receive a verification code via SMS.\n")
|
||||||
fmt.Printf("Enter verification code> ")
|
fmt.Printf("Enter verification code> ")
|
||||||
authCode := config.ReadLine()
|
authCode := config.ReadLine()
|
||||||
authCode = strings.Replace(authCode, "-", "", -1) // the sms received contains a pair of 3 digit numbers seperated by '-' but wants a single 6 digit number
|
authCode = strings.Replace(authCode, "-", "", -1) // the sms received contains a pair of 3 digit numbers seperated by '-' but wants a single 6 digit number
|
||||||
@@ -180,23 +183,49 @@ func init() {
|
|||||||
// finally save them in the config
|
// finally save them in the config
|
||||||
err = oauthutil.PutToken(name, m, &token, true)
|
err = oauthutil.PutToken(name, m, &token, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error while setting token: %s", err)
|
log.Fatalf("Error while saving token: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\nDo you want to use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?\n\n")
|
||||||
|
if config.Confirm() {
|
||||||
|
oAuthClient, _, err := oauthutil.NewClient(name, m, oauthConfig)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to load oAuthClient: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srv = rest.NewClient(oAuthClient).SetRoot(rootURL)
|
||||||
|
|
||||||
|
acc, err := getAccountInfo(srv, username)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error getting devices: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Please select the device to use. Normally this will be Jotta\n")
|
||||||
|
var deviceNames []string
|
||||||
|
for i := range acc.Devices {
|
||||||
|
deviceNames = append(deviceNames, acc.Devices[i].Name)
|
||||||
|
}
|
||||||
|
result := config.Choose("Devices", deviceNames, nil, false)
|
||||||
|
m.Set(configDevice, result)
|
||||||
|
|
||||||
|
dev, err := getDeviceInfo(srv, path.Join(username, result))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error getting Mountpoint: %s", err)
|
||||||
|
}
|
||||||
|
if len(dev.MountPoints) == 0 {
|
||||||
|
log.Fatalf("No Mountpoints found for this device.")
|
||||||
|
}
|
||||||
|
fmt.Printf("Please select the mountpoint to user. Normally this will be Archive\n")
|
||||||
|
var mountpointNames []string
|
||||||
|
for i := range dev.MountPoints {
|
||||||
|
mountpointNames = append(mountpointNames, dev.MountPoints[i].Name)
|
||||||
|
}
|
||||||
|
result = config.Choose("Mountpoints", mountpointNames, nil, false)
|
||||||
|
m.Set(configMountpoint, result)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Options: []fs.Option{{
|
Options: []fs.Option{{
|
||||||
Name: configUsername,
|
Name: configUsername,
|
||||||
Help: "User Name:",
|
Help: "User Name:",
|
||||||
}, {
|
|
||||||
Name: "mountpoint",
|
|
||||||
Help: "The mountpoint to use.",
|
|
||||||
Required: true,
|
|
||||||
Examples: []fs.OptionExample{{
|
|
||||||
Value: "Sync",
|
|
||||||
Help: "Will be synced by the official client.",
|
|
||||||
}, {
|
|
||||||
Value: "Archive",
|
|
||||||
Help: "Archive",
|
|
||||||
}},
|
|
||||||
}, {
|
}, {
|
||||||
Name: "md5_memory_limit",
|
Name: "md5_memory_limit",
|
||||||
Help: "Files bigger than this will be cached on disk to calculate the MD5 if required.",
|
Help: "Files bigger than this will be cached on disk to calculate the MD5 if required.",
|
||||||
@@ -224,6 +253,7 @@ func init() {
|
|||||||
// Options defines the configuration for this backend
|
// Options defines the configuration for this backend
|
||||||
type Options struct {
|
type Options struct {
|
||||||
User string `config:"user"`
|
User string `config:"user"`
|
||||||
|
Device string `config:"device"`
|
||||||
Mountpoint string `config:"mountpoint"`
|
Mountpoint string `config:"mountpoint"`
|
||||||
MD5MemoryThreshold fs.SizeSuffix `config:"md5_memory_limit"`
|
MD5MemoryThreshold fs.SizeSuffix `config:"md5_memory_limit"`
|
||||||
HardDelete bool `config:"hard_delete"`
|
HardDelete bool `config:"hard_delete"`
|
||||||
@@ -331,18 +361,31 @@ func (f *Fs) readMetaDataForPath(path string) (info *api.JottaFile, err error) {
|
|||||||
return &result, nil
|
return &result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getAccountInfo retrieves account information
|
// getAccountInfo queries general information about the account.
|
||||||
func (f *Fs) getAccountInfo() (info *api.AccountInfo, err error) {
|
// Takes rest.Client and username as parameter to be easily usable
|
||||||
|
// during config
|
||||||
|
func getAccountInfo(srv *rest.Client, username string) (info *api.AccountInfo, err error) {
|
||||||
opts := rest.Opts{
|
opts := rest.Opts{
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: urlPathEscape(f.user),
|
Path: urlPathEscape(username),
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp *http.Response
|
_, err = srv.CallXML(&opts, nil, &info)
|
||||||
err = f.pacer.Call(func() (bool, error) {
|
if err != nil {
|
||||||
resp, err = f.srv.CallXML(&opts, nil, &info)
|
return nil, err
|
||||||
return shouldRetry(resp, err)
|
}
|
||||||
})
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDeviceInfo queries Information about a jottacloud device
|
||||||
|
func getDeviceInfo(srv *rest.Client, path string) (info *api.JottaDevice, err error) {
|
||||||
|
opts := rest.Opts{
|
||||||
|
Method: "GET",
|
||||||
|
Path: urlPathEscape(path),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = srv.CallXML(&opts, nil, &info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -351,12 +394,18 @@ func (f *Fs) getAccountInfo() (info *api.AccountInfo, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setEndpointUrl reads the account id and generates the API endpoint URL
|
// setEndpointUrl reads the account id and generates the API endpoint URL
|
||||||
func (f *Fs) setEndpointURL(mountpoint string) (err error) {
|
func (f *Fs) setEndpointURL() (err error) {
|
||||||
info, err := f.getAccountInfo()
|
info, err := getAccountInfo(f.srv, f.user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to get endpoint url")
|
return errors.Wrap(err, "failed to get endpoint url")
|
||||||
}
|
}
|
||||||
f.endpointURL = urlPathEscape(path.Join(info.Username, defaultDevice, mountpoint))
|
if f.opt.Device == "" {
|
||||||
|
f.opt.Device = defaultDevice
|
||||||
|
}
|
||||||
|
if f.opt.Mountpoint == "" {
|
||||||
|
f.opt.Mountpoint = defaultMountpoint
|
||||||
|
}
|
||||||
|
f.endpointURL = urlPathEscape(path.Join(info.Username, f.opt.Device, f.opt.Mountpoint))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,9 +492,6 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
|
|||||||
oauthConfig.ClientID = clientID
|
oauthConfig.ClientID = clientID
|
||||||
oauthConfig.ClientSecret = obscure.MustReveal(clientSecret)
|
oauthConfig.ClientSecret = obscure.MustReveal(clientSecret)
|
||||||
|
|
||||||
// add jottacloud to the long list of sites that don't follow the oauth spec correctly
|
|
||||||
oauth2.RegisterBrokenAuthHeaderProvider("https://www.jottacloud.com/")
|
|
||||||
|
|
||||||
// the oauth client for the api servers needs
|
// the oauth client for the api servers needs
|
||||||
// a filter to fix the grant_type issues (see above)
|
// a filter to fix the grant_type issues (see above)
|
||||||
baseClient := fshttp.NewClient(fs.Config)
|
baseClient := fshttp.NewClient(fs.Config)
|
||||||
@@ -484,7 +530,7 @@ func NewFs(name, root string, m configmap.Mapper) (fs.Fs, error) {
|
|||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
err = f.setEndpointURL(opt.Mountpoint)
|
err = f.setEndpointURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "couldn't get account info")
|
return nil, errors.Wrap(err, "couldn't get account info")
|
||||||
}
|
}
|
||||||
@@ -742,6 +788,9 @@ 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
|
// The new object may have been created if an error is returned
|
||||||
func (f *Fs) Put(in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) (fs.Object, error) {
|
func (f *Fs) Put(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(), src.Size())
|
o := f.createObject(src.Remote(), src.ModTime(), src.Size())
|
||||||
return o, o.Update(in, src, options...)
|
return o, o.Update(in, src, options...)
|
||||||
}
|
}
|
||||||
@@ -1005,7 +1054,7 @@ func (f *Fs) PublicLink(remote string) (link string, err error) {
|
|||||||
|
|
||||||
// About gets quota information
|
// About gets quota information
|
||||||
func (f *Fs) About() (*fs.Usage, error) {
|
func (f *Fs) About() (*fs.Usage, error) {
|
||||||
info, err := f.getAccountInfo()
|
info, err := getAccountInfo(f.srv, f.user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,26 +25,20 @@ n) New remote
|
|||||||
s) Set configuration password
|
s) Set configuration password
|
||||||
q) Quit config
|
q) Quit config
|
||||||
n/s/q> n
|
n/s/q> n
|
||||||
name> remote
|
name> jotta
|
||||||
Type of storage to configure.
|
Type of storage to configure.
|
||||||
Enter a string value. Press Enter for the default ("").
|
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]
|
[snip]
|
||||||
13 / JottaCloud
|
14 / JottaCloud
|
||||||
\ "jottacloud"
|
\ "jottacloud"
|
||||||
[snip]
|
[snip]
|
||||||
Storage> jottacloud
|
Storage> jottacloud
|
||||||
User Name
|
** See help for jottacloud backend at: https://rclone.org/jottacloud/ **
|
||||||
|
|
||||||
|
User Name:
|
||||||
Enter a string value. Press Enter for the default ("").
|
Enter a string value. Press Enter for the default ("").
|
||||||
user> user
|
user> user@email.tld
|
||||||
The mountpoint to use.
|
|
||||||
Enter a string value. Press Enter for the default ("").
|
|
||||||
Choose a number from below, or type in your own value
|
|
||||||
1 / Will be synced by the official client.
|
|
||||||
\ "Sync"
|
|
||||||
2 / Archive
|
|
||||||
\ "Archive"
|
|
||||||
mountpoint> Archive
|
|
||||||
Edit advanced config? (y/n)
|
Edit advanced config? (y/n)
|
||||||
y) Yes
|
y) Yes
|
||||||
n) No
|
n) No
|
||||||
@@ -58,16 +52,35 @@ Rclone has it's own Jottacloud API KEY which works fine as long as one only uses
|
|||||||
y) Yes
|
y) Yes
|
||||||
n) No
|
n) No
|
||||||
y/n> y
|
y/n> y
|
||||||
Your Jottacloud password is only required during config and will not be stored.
|
Your Jottacloud password is only required during setup and will not be stored.
|
||||||
password:
|
password:
|
||||||
|
|
||||||
|
Do you want to use a non standard device/mountpoint e.g. for accessing files uploaded using the official Jottacloud client?
|
||||||
|
|
||||||
|
y) Yes
|
||||||
|
n) No
|
||||||
|
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
|
||||||
|
1 > DESKTOP-3H31129
|
||||||
|
2 > test1
|
||||||
|
3 > Jotta
|
||||||
|
Devices> 3
|
||||||
|
Please select the mountpoint to user. Normally this will be Archive
|
||||||
|
Choose a number from below, or type in an existing value
|
||||||
|
1 > Archive
|
||||||
|
2 > Shared
|
||||||
|
3 > Sync
|
||||||
|
Mountpoints> 1
|
||||||
--------------------
|
--------------------
|
||||||
[remote]
|
[jotta]
|
||||||
type = jottacloud
|
type = jottacloud
|
||||||
user = olihey
|
user = 0xC4KE@gmail.com
|
||||||
mountpoint = Archive
|
|
||||||
client_id = .....
|
client_id = .....
|
||||||
client_secret = ........
|
client_secret = ........
|
||||||
token = {........}
|
token = {........}
|
||||||
|
device = Jotta
|
||||||
|
mountpoint = Archive
|
||||||
--------------------
|
--------------------
|
||||||
y) Yes this is OK
|
y) Yes this is OK
|
||||||
e) Edit this remote
|
e) Edit this remote
|
||||||
@@ -88,6 +101,11 @@ To copy a local directory to an Jottacloud directory called backup
|
|||||||
|
|
||||||
rclone copy /home/source remote:backup
|
rclone copy /home/source remote: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, Sync and Shared mountpoints. In most cases you'll want to use the Jotta/Archive device/mounpoint however if you want to access files uploaded by the official rclone provides the option to select other devices and mountpoints during config.
|
||||||
|
|
||||||
### --fast-list ###
|
### --fast-list ###
|
||||||
|
|
||||||
This remote supports `--fast-list` which allows you to use fewer
|
This remote supports `--fast-list` which allows you to use fewer
|
||||||
@@ -149,20 +167,6 @@ User Name:
|
|||||||
- Type: string
|
- Type: string
|
||||||
- Default: ""
|
- Default: ""
|
||||||
|
|
||||||
#### --jottacloud-mountpoint
|
|
||||||
|
|
||||||
The mountpoint to use.
|
|
||||||
|
|
||||||
- Config: mountpoint
|
|
||||||
- Env Var: RCLONE_JOTTACLOUD_MOUNTPOINT
|
|
||||||
- Type: string
|
|
||||||
- Default: ""
|
|
||||||
- Examples:
|
|
||||||
- "Sync"
|
|
||||||
- Will be synced by the official client.
|
|
||||||
- "Archive"
|
|
||||||
- Archive
|
|
||||||
|
|
||||||
### Advanced Options
|
### Advanced Options
|
||||||
|
|
||||||
Here are the advanced options specific to jottacloud (JottaCloud).
|
Here are the advanced options specific to jottacloud (JottaCloud).
|
||||||
|
|||||||
Reference in New Issue
Block a user