Using Ubuntu One’s Cloud API

I recently finished up a branch adding support to the backup program duplicity for Ubuntu One. I thought I’d write up how I did it, because some things weren’t obvious.

My needs were simple: Python versions of get, put, delete, and list operations.

Code snippets under GPL-2+ license. These don’t have any error handling, but that’s simple enough to add. Note that for the list and put snippets you will need to either use trunk or a release greater than 0.2.0 of the ubuntuone-couch Python module.

If parts of the snippets are confusing, it may help to consult the official documentation.

Log In

First, you have to log the user into Ubuntu One. There’s a utility class in ubuntuone.platform.credentials to help with this. It’s designed to be asynchronous, but for our simple purposes, we’ll fake synchronicity with a main loop.

_login_success = False
def login(self):
    from gobject import MainLoop
    from dbus.mainloop.glib import DBusGMainLoop
    from ubuntuone.platform.credentials import CredentialsManagementTool

    global _login_success
    _login_success = False

    DBusGMainLoop(set_as_default=True)
    loop = MainLoop()

    def quit(result):
            global _login_success
	    loop.quit()
	    if result:
		    _login_success = True

    cd = CredentialsManagementTool()
    d = cd.login()
    d.addCallbacks(quit)
    loop.run()
    return _login_success

Create Volume

Another piece of set up before we can get to the good stuff is to create a volume if needed. A volume is just a directory in Ubuntu One that can be synchronized (but isn’t by default). It’s OK to attempt to create a volume multiple times, but Ubuntu One will report back an error if you try to create a nested volume.

Note the auth.request call. This adds the required OAuth header to our request so that we can successfully authorize with the server. This is why we must be logged in first, to have access to the credentials that auth.request uses.

def create_volume(path):
    import ubuntuone.couch.auth as auth
    import urllib
    base = "https://one.ubuntu.com/api/file_storage/v1/volumes/~/"
    return auth.request(base + urllib.quote(path), http_method="PUT")

Delete

Alright, we can get down to brass tacks now that we are logged in and have a volume in which to work. Let’s start simple with a file delete request.

def delete(path):
    import ubuntuone.couch.auth as auth
    import urllib
    base = "https://one.ubuntu.com/api/file_storage/v1/~/"
    return auth.request(base + urllib.quote(path), http_method="DELETE")

That was easy!

List

Listing involves requesting the metadata for the parent directory / volume and explicitly asking for information about its children.

def list(path):
    import json
    import ubuntuone.couch.auth as auth
    import urllib
    base = "https://one.ubuntu.com/api/file_storage/v1/~/"
    url = base + urllib.quote(path) + "?include_children=true"
    answer = auth.request(url)
    filelist = []
    node = json.loads(answer[1])
    if node.get('has_children') == True:
        for child in node.get('children'):
            child_path = urllib.unquote(child.get('path')).lstrip('/')
            filelist += [child_path]
    return filelist

Get

This is a little tricky. You have to first ask for metadata about the file to get the actual content path. Then you have to hit files.one.ubuntu.com to get the data itself.

Note we don’t quote the twiddle in the content_path field. It needs to be unquoted.

def get(remote, local):
    import json
    import ubuntuone.couch.auth as auth
    import urllib
    base = "https://one.ubuntu.com/api/file_storage/v1/~/"
    answer = auth.request(base + urllib.quote(remote))
    node = json.loads(answer[1])
    base = "https://files.one.ubuntu.com"
    url = base + urllib.quote(node.get('content_path'), safe="~")
    answer = auth.request(url)
    f = open(local, 'wb')
    f.write(answer[1])

Put

Last, but not least, putting a file on the server. Note that you must specify a content type and length.

def put(local, remote):
    import json
    import ubuntuone.couch.auth as auth
    import mimetypes
    import urllib
    base = "https://one.ubuntu.com/api/file_storage/v1/~/"
    answer = auth.request(base + urllib.quote(remote),
                          http_method="PUT",
                          request_body='{"kind":"file"}')
    node = json.loads(answer[1])
    data = bytearray(open(local, 'rb').read())
    size = len(data)
    content_type = mimetypes.guess_type(local)[0]
    content_type = content_type or 'application/octet-stream'
    headers = {"Content-Length": str(size),
	       "Content-Type": content_type}
    base = "https://files.one.ubuntu.com"
    url = base + urllib.quote(node.get('content_path'), safe="~")
    return auth.request(url, http_method="PUT",
                        headers=headers, request_body=data)

Odds and Ends

Hope that was helpful!

You should be able to figure out how to do other things you’ll need from these snippets and the official documentation. Note that as of this writing, parts of that documentation are out of date. For the moment, trust my snippets over the documentation! I’m told the documentation is being updated.

Note that in terms of HTTP error codes, Ubuntu One seems to return 500 for out-of-space. And if you request a malformed path (like, with two slashes in it), it likes to give back an error code with a login page as an HTML payload.

One thought on “Using Ubuntu One’s Cloud API”

  1. Hello ,Could you tell me IS the ubtuntu one files API be available In China? Do you have a simple example in C# language ,there is a Firewall in our country I can not find it ,thank you very much。

Comments are closed.