Skip to content
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

Merged
merged 3 commits into from
Aug 10, 2018
Merged

Add support for Jottacloud #2433

merged 3 commits into from
Aug 10, 2018

Conversation

buengese
Copy link
Member

@buengese buengese commented Aug 1, 2018

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

  • Add config opions for device and mountpoint
  • Implement the optional Purger, Copier, Mover, DirMover interfaces
  • Maybe look into the newer upload API
  • Cleanup

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.

@jkaberg
Copy link

jkaberg commented Aug 2, 2018

Wonderfull @buengese. If you wan't I could try and reverse engineer the jotta-cli tool API, however I see they're missing some functions to make it viable.

Looking forward to see what you come up with, let me know if you need beta testing done.

@ncw
Copy link
Member

ncw commented Aug 2, 2018

That is looking really good :-)

The tests are failing because errcheck is failing due to Sprintf parameters.

You can use

 make build_dep

To install the checkers locally

Then

 make check

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.

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?
Copy link

@jkaberg jkaberg Aug 2, 2018

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).

Copy link
Member Author

@buengese buengese Aug 2, 2018

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.

Copy link

@jkaberg jkaberg Aug 2, 2018

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.

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"
Copy link

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?

Copy link
Member Author

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.

Copy link

@jkaberg jkaberg Aug 2, 2018

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

@buengese
Copy link
Member Author

buengese commented Aug 3, 2018

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.

@jkaberg
Copy link

jkaberg commented Aug 4, 2018

@buengese I made a local build and tried it, however it fails on,

2018/08/04 10:35:39 Failed to create file system for "jcloud:/test": couldn't get account info: strconv.ParseUint: parsing "-1": invalid syntax

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/

<user time="2018-08-04-T08:37:48Z" host="dn-111">
<script/>
<username>get-12345678</username>
<account-type>unlimited</account-type>
<locked>false</locked>
<capacity>-1</capacity>
<max-devices>-1</max-devices>
<max-mobile-devices>-1</max-mobile-devices>
<usage>1231231231234</usage>
<read-locked>false</read-locked>
<write-locked>false</write-locked>
<quota-write-locked>false</quota-write-locked>
<enable-sync>false</enable-sync>
<devices>
<device>
<name xml:space="preserve">Jotta</name>
<display_name xml:space="preserve">Jotta</display_name>
<type>JOTTA</type>
<sid>XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX</sid>
<size>1231231231234</size>
<modified>2018-08-03-T23:47:50Z</modified>
</device>
</devices>
</user>

@buengese buengese force-pushed the jottacloud branch 2 times, most recently from 5b5f0e9 to 01c6d27 Compare August 4, 2018 19:32
@buengese
Copy link
Member Author

buengese commented Aug 4, 2018

@jkaberg It's fixed. Didn't expect negative values there.

@jkaberg
Copy link

jkaberg commented Aug 5, 2018

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!

Copy link
Member

@ncw ncw left a 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!

if err != nil {
return err
}
// Parse the time format in multiple possible ways
Copy link
Member

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

minSleep = 10 * time.Millisecond
maxSleep = 2 * time.Second
decayConstant = 2 // bigger for slower decay, exponential
defaultDevice = "Jotta" // TODO ? : Make this a config Option?
Copy link
Member

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?

Copy link
Member Author

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)

Copy link

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

if err != nil {
return err
}
// Parse the time format in multiple possible ways
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment looks obsolete

switch apiErr.StatusCode {
case http.StatusNotFound:
return nil, fs.ErrorObjectNotFound
case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther:
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a leftover.

CanHaveEmptyDirectories: true,
}).Fill(f)

if user != "" || pass != "" {
Copy link
Member

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 == ""

Copy link
Member Author

@buengese buengese Aug 6, 2018

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)
Copy link
Member

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.

Method: "POST",
Path: o.filePath(),
Body: in,
ContentType: "application/octet-stream",
Copy link
Member

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.

opts.ExtraHeaders["JMd5"] = md5
opts.Parameters.Set("cphash", md5)

// TODO ? : If MD5 unsupported calculate MD5 ourself
Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

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.

Copy link
Member Author

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 {
Copy link
Member

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!

Copy link
Member Author

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.

Copy link
Member

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).

@ncw
Copy link
Member

ncw commented Aug 6, 2018

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

~/go/src/github.com/ncw/rclone/fs/sync$ go test -v -run TestServerSideCopy -remote TestJottacloud:
=== RUN   TestServerSideCopy
2018/08/06 19:22:11 ERROR : sub dir/hello world: Failed to copy: copy failed: Error 404: no.jotta.backup.errors.NoSuchPathException: Destination parent path /8vopdox6wjnjmz3l0mfiitae/Jotta/Sync/rclone-test-lofokod4weqesez0yuyaduv1/sub dir does not exist (Not Found)
2018/08/06 19:22:11 purge failed: directory not found
--- FAIL: TestServerSideCopy (7.17s)
	run.go:173: Remote "jottacloud root 'rclone-test-wuxaqog6sazecah5fitalav2'", Local "Local file system at /tmp/rclone454131250", Modify Window "1s"
	sync_test.go:118: Server side copy (if possible) jottacloud root 'rclone-test-wuxaqog6sazecah5fitalav2' -> jottacloud root 'rclone-test-lofokod4weqesez0yuyaduv1'
	require.go:794: 
			Error Trace:	sync_test.go:121
			Error:      	Received unexpected error:
			            	Error 404: no.jotta.backup.errors.NoSuchPathException: Destination parent path /8vopdox6wjnjmz3l0mfiitae/Jotta/Sync/rclone-test-lofokod4weqesez0yuyaduv1/sub dir does not exist (Not Found)
			            	copy failed
			            	github.com/ncw/rclone/backend/jottacloud.(*Fs).Copy
			            		/home/ncw/go/src/github.com/ncw/rclone/backend/jottacloud/jottacloud.go:538
			            	github.com/ncw/rclone/fs.(Copier).Copy-fm
			            		/home/ncw/go/src/github.com/ncw/rclone/fs/fs.go:527
			            	github.com/ncw/rclone/fs/operations.Copy
			            		/home/ncw/go/src/github.com/ncw/rclone/fs/operations/operations.go:269
			            	github.com/ncw/rclone/fs/sync.(*syncCopyMove).pairCopyOrMove
			            		/home/ncw/go/src/github.com/ncw/rclone/fs/sync/sync.go:306
			            	runtime.goexit
			            		/opt/go/go1.10/src/runtime/asm_amd64.s:2361
			Test:       	TestServerSideCopy
FAIL
exit status 1
FAIL	github.com/ncw/rclone/fs/sync	8.898s

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.

@buengese buengese force-pushed the jottacloud branch 2 times, most recently from 82050fe to aef2d33 Compare August 7, 2018 18:53
@buengese buengese changed the title [WIP] Add support for Jottacloud Add support for Jottacloud Aug 7, 2018
Copy link

@jkaberg jkaberg left a 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 😄


### Limitations ###

Note that Box is case insensitive so you can't have a file called
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Box? Jottacloud? 😄

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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OneDrive?

@ncw
Copy link
Member

ncw commented Aug 10, 2018

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).

Jottacloud exhibits some inconsistent behaviours regarding deleted files and folders which may cause Copy, Move and DirMove operations to previously deleted paths to fail. Emptying the trash should help in such cases

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 :-)

@ncw ncw merged commit 007c775 into rclone:master Aug 10, 2018
@ncw
Copy link
Member

ncw commented Aug 10, 2018

I stuck a post on the forum encouraging people to test it: https://forum.rclone.org/t/jottacloud-now-in-beta/6449

@buengese buengese deleted the jottacloud branch August 10, 2018 21:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants