-
-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for Jottacloud #2433
Conversation
That is looking really good :-) The tests are failing because errcheck is failing due to Sprintf parameters. You can use
To install the checkers locally Then
To run them. What is the status of the integration tests? Are they passing? If you can get the tests passing then I'll give it a review and try it locally. Well done getting this far, it is a lot of work. |
backend/jottacloud/jottacloud.go
Outdated
maxSleep = 2 * time.Second | ||
decayConstant = 2 // bigger for slower decay, exponential | ||
defaultDevice = "Jotta" // TODO ? : Make this a config Option? | ||
defaultMountpoint = "Sync" // TODO ? : Make this a config Option? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
defaultDevice usually never changes so IDK, but defaultMountpoint could so it makes sense to make it configureable. It makes even more sense to change defaultMountpoint to something other than Sync as thats the default folder for the official client (things placed in this folder will get synced to all official clients).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree on device. It's actually possible to create you own mountpoints but I wasn't sure if and how i should implement that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess if you were to implement that, it could be treated the same way as folders? Eg an mountpoint "is just another folder", if that makes sense.
backend/jottacloud/jottacloud.go
Outdated
defaultMountpoint = "Sync" // TODO ? : Make this a config Option? | ||
rootURL = "https://www.jottacloud.com/jfs/" | ||
//newApiRootUrl = "https://api.jottacloud.com" | ||
//newUploadUrl = "https://up-no-001.jottacloud.com" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I reversed the corresponding upload url for the JFS api (https://www.jottacloud.com/jfs/) one needed to use https://up.jottacloud.com/ to upload (ref https://github.com/havardgulldahl/jottalib/blob/master/src/jottalib/JFS.py#L1128).
However I can't seem to find it here, did this change later on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
both seem to work fine. I'm pretty sure I've seen both being used but I can change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to change it if it works I guess? Was just curious 😃
But it would be preferable to mimic the official client, might be a reason behind it using the up.jottacloud.com endpoint
I implemented the Purger, Copier, Mover, DirMover interfaces. Current implementation is kind a redundant will still be simplified. All unit tests pass however the TestCopyFile integration test fails if run together with the other tests (It does pass if run on it's own). This seems to be a problem with how Jottacloud handles deleted files. If I find some free time I'll look into it tomorrow. Everything else should be in a reviewable state and some manual testing could also be done. |
@buengese I made a local build and tried it, however it fails on,
Seem's to be related to https://github.com/buengese/rclone/blob/jottacloud/backend/jottacloud/api/types.go#L102 which is -1 in my case (unlimited) GET https://www.jottacloud.com/jfs/get-12345678/
|
5b5f0e9
to
01c6d27
Compare
@jkaberg It's fixed. Didn't expect negative values there. |
Seem's to be working as expected (copy, move etc), haven't tried mount or anything more advanced but I will soon enough 😄 Well done @buengese! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a really excellent piece of work ⭐
I've put a few comments, queries and things to fix up inline which don't detract at all from a first rate pull request :-)
I'm going to write more things which need to be done on the PR in a moment as this box is very small for typing in!
backend/jottacloud/api/types.go
Outdated
if err != nil { | ||
return err | ||
} | ||
// Parse the time format in multiple possible ways |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment looks like a leftover
backend/jottacloud/jottacloud.go
Outdated
minSleep = 10 * time.Millisecond | ||
maxSleep = 2 * time.Second | ||
decayConstant = 2 // bigger for slower decay, exponential | ||
defaultDevice = "Jotta" // TODO ? : Make this a config Option? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does it do? Might the user need to change it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My information on this is not exactly clear either but it seems it was supposed to identify different computers for their own sync functionality. (The Jotta Device is used by the WebUI program implementing the Jottacloud API)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been using Jotta and the api for many years, this has never changed over the years. Neither with Desktop/WebUI usage or mobile devices.
I guess this is just some legacy stuff that never changed
backend/jottacloud/api/types.go
Outdated
if err != nil { | ||
return err | ||
} | ||
// Parse the time format in multiple possible ways |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment looks obsolete
backend/jottacloud/jottacloud.go
Outdated
switch apiErr.StatusCode { | ||
case http.StatusNotFound: | ||
return nil, fs.ErrorObjectNotFound | ||
case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need these other statuses? I would have thought http.StatusNotFound
should cover it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a leftover.
backend/jottacloud/jottacloud.go
Outdated
CanHaveEmptyDirectories: true, | ||
}).Fill(f) | ||
|
||
if user != "" || pass != "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is anonymous access possible to Jottacloud? If not then this should probably error if user == "" || pass == ""
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't. This appears to be a copy-paste leftover.
|
||
var resp *http.Response | ||
err = f.pacer.Call(func() (bool, error) { | ||
resp, err = f.srv.Call(&opts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are using Call you need to explicitly close the resp.Body otherwise you'll leak a file descriptor - see its docs.
You can do this by using by using CallXML(&opts, nil, nil) or by setting NoResponse: true in opts.
backend/jottacloud/jottacloud.go
Outdated
Method: "POST", | ||
Path: o.filePath(), | ||
Body: in, | ||
ContentType: "application/octet-stream", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you probably want fs.MimeType(src)
here if that is setting the content type of the uploaded object.
backend/jottacloud/jottacloud.go
Outdated
opts.ExtraHeaders["JMd5"] = md5 | ||
opts.Parameters.Set("cphash", md5) | ||
|
||
// TODO ? : If MD5 unsupported calculate MD5 ourself |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calculating the MD5 involves streaming the file into a temporary file and we usually don't bother unless the upload absolutely requires it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I expected that this. The MD5 seems to be required only if you want the uploads to be resumable.
@@ -0,0 +1,84 @@ | |||
/* | |||
Translate file names for JottaCloud adapted from OneDrive |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you copy and then adapt the tests from backent/onedrive/replace_test.go too please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
@@ -147,6 +147,10 @@ func FixRangeOption(options []OpenOption, size int64) { | |||
x = &RangeOption{Start: size - x.End, End: -1} | |||
options[i] = x | |||
} | |||
if x.End > size { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you stick this in a separate commit please (doesn't need to be a new PR), update the comment for this function and justify why you need to make the change in the commit message - thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one might actually need a bit of discussion. I added this as quick fix for passing the TestObjectOpenRange unit test. Specificly this one:
{fs.RangeOption{Start: 81, End: 100000}, 81, 100}
As far as I understand RFC7233 this request should actually fail with Range not Satisfiable and on Jottacloud it does. However all the other backends implemented so far seem to be fine with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting... It is a bit vague the RFC isn't it. Especially this bit:
Thus, clients cannot depend on receiving a 416 (Range Not Satisfiable) response even when it is most appropriate.
I'm happy for this to go in a separate commit and it will get run through all the integration tests for the other backends so we can see if it breaks anything else (I don't think it will).
An impressive piece of work - thank you! And most importantly it works! I ran some tests by hand copying things about and it worked very well. The unit tests pass. The operations test pass. The sync tests give one test failure which seems repeatable
So I would say, bar a few little cleanups and fixes the code is done :-) So looking at the writing a new backend checklist I'd say you've got down to the part beginning "Add your fs to the docs..." so nearly done! Let me know if you want any help with anything. I'm hoping we can land this in time for the 1.43 release at the end of the month. |
82050fe
to
aef2d33
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found 2 typo's you might want to change 😄
docs/content/jottacloud.md
Outdated
|
||
### Limitations ### | ||
|
||
Note that Box is case insensitive so you can't have a file called |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Box? Jottacloud? 😄
docs/content/jottacloud.md
Outdated
Note that Box is case insensitive so you can't have a file called | ||
"Hello.doc" and one called "hello.doc". | ||
|
||
There are quite a few characters that can't be in OneDrive file names. Rclone will map these names to and from an identical looking unicode equivalent. For example if a file has a ? in it will be mapped to ? instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OneDrive?
I've looked through all that - and it looks really good - well done! The failing test passed when I tried it. I don't know whether you fixed it or whether it is to do with this (from the docs).
I'm going to merge this now. You can see the daily integration tests run here: https://pub.rclone.org/integration-tests/ - so we can keep an eye on what happens there If you'd like to be added to the project as a maintainer then drop me an email to nick@craig-wood.com to discuss. Thank you very much for a great contribution :-) |
I stuck a post on the forum encouraging people to test it: https://forum.rclone.org/t/jottacloud-now-in-beta/6449 |
Summary
This is my work adding support for Jottacloud as requested in #307. The basic Interfaces are implemented and working but there are still some things to do.
Some open stuff.
The Jottacloud API situation:
What's left to do
On a final note this is pretty much the first time I have written anything in go so there may be some mistakes / bad practices in here.