Up to the previous post, without counting the long-format posts, and the posts translated to spanish, this blog contained 1463149 (prime!) characters. It took me 4448 days to do that, which means I wrote 328 characters a day.
This is what I talk about, which means I am boring:
BTW: this post is exactly 328 characters long now.
One of the nice things about working at Canonical is that we produce open source software.
I, specifically, work in the team that does the desktop clients for Ubuntu One which is a really cool job, and a really cool piece
of software. However, one thing not enough people know, is that we offer damn nice APIs
for developers. We have to, since all our client code is open source, so we need those
APIs for ourselves.
So, here is a small tutorial about using some of those APIs. I did it using Python and
PyQt for several reasons:
Both are great tools for prototyping
Both have good support for the required stuff (DBus, HTTP, OAuth)
It's what I know and enjoy. Since I did this code on a sunday, I am
not going to use other things.
Having said that, there is nothing python-specific or Qt-specific in the code. Where
I do a HTTP request using QtNetwork, you are free to use libsoup, or whatever.
So, on to the nuts and bolts. The main pieces of Ubuntu One, from a infrastructure
perspective, are Ubuntu SSO Client, that handles user registration and login, and
SyncDaemon, which handles file synchronization.
To interact with them, on Linux, they offer DBus interfaces. So, for example, this is
a fragment of code showing a way to get the Ubuntu One credentials (this would normally
be part of an object's __init__):
# Get the session bus
bus = dbus.SessionBus()
:
:
:
# Get the credentials proxy and interface
self.creds_proxy = bus.get_object("com.ubuntuone.Credentials",
"/credentials",
follow_name_owner_changes=True)
# Connect to signals so you get a call when something
# credential-related happens
self.creds_iface = dbus.Interface(self.creds_proxy,
"com.ubuntuone.CredentialsManagement")
self.creds_proxy.connect_to_signal('CredentialsFound',
self.creds_found)
self.creds_proxy.connect_to_signal('CredentialsNotFound',
self.creds_not_found)
self.creds_proxy.connect_to_signal('CredentialsError',
self.creds_error)
# Call for credentials
self._credentials = None
self.get_credentials()
You may have noticed that get_credentials doesn't actually return the credentials. What
it does is, it tells SyncDaemon to fetch the credentials, and then, when/if they are there,
one of the signals will be emitted, and one of the connected methods will be called. This
is nice, because it means you don't have to worry about your app blocking while SyncDaemon
is doing all this.
But what's in those methods we used? Not much, really!
def get_credentials(self):
# Do we have them already? If not, get'em
if not self._credentials:
self.creds_proxy.find_credentials()
# Return what we've got, could be None
return self._credentials
def creds_found(self, data):
# Received credentials, save them.
print "creds_found", data
self._credentials = data
# Don't worry about get_quota yet ;-)
if not self._quota_info:
self.get_quota()
def creds_not_found(self, data):
# No credentials, remove old ones.
print "creds_not_found", data
self._credentials = None
def creds_error(self, data):
# No credentials, remove old ones.
print "creds_error", data
self._credentials = None
So, basically, self._credentials will hold a set of credentials, or None. Congratulations, we
are now logged into Ubuntu One, so to speak.
So, let's do something useful! How about asking for how much free space there is in
the account? For that, we can't use the local APIs, we have to connect to the servers, who
are, after all, the ones who decide if you are over quota or not.
Access is controlled via OAuth. So, to access the API, we need to sign our requests. Here
is how it's done. It's not particularly enlightening, and I did not write it, I just use it:
def sign_uri(self, uri, parameters=None):
# Without credentials, return unsigned URL
if not self._credentials:
return uri
if isinstance(uri, unicode):
uri = bytes(iri2uri(uri))
print "uri:", uri
method = "GET"
credentials = self._credentials
consumer = oauth.OAuthConsumer(credentials["consumer_key"],
credentials["consumer_secret"])
token = oauth.OAuthToken(credentials["token"],
credentials["token_secret"])
if not parameters:
_, _, _, _, query, _ = urlparse(uri)
parameters = dict(cgi.parse_qsl(query))
request = oauth.OAuthRequest.from_consumer_and_token(
http_url=uri,
http_method=method,
parameters=parameters,
oauth_consumer=consumer,
token=token)
sig_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
request.sign_request(sig_method, consumer, token)
print "SIGNED:", repr(request.to_url())
return request.to_url()
And how do we ask for the quota usage? By accessing the https://one.ubuntu.com/api/quota/ entry point
with the proper authorization, we would get a JSON dictionary with total and used space.
So, here's a simple way to do it:
# This is on __init__
self.nam = QtNetwork.QNetworkAccessManager(self,
finished=self.reply_finished)
:
:
:
def get_quota(self):
"""Launch quota info request."""
uri = self.sign_uri(QUOTA_API)
url = QtCore.QUrl()
url.setEncodedUrl(uri)
self.nam.get(QtNetwork.QNetworkRequest(url))
Again, see how get_quota doesn't return the quota? What happens is that get_quota will
launch a HTTP request to the Ubuntu One servers, which will, eventually, reply with the data.
You don't want your app to block while you do that. So, QNetworkAccessManager will call
self.reply_finished when it gets the response:
What else would be nice to have? How about getting a call whenever the status of syncdaemon
changes? For example, when sync is up to date, or when you get disconnected? Again, those are
DBus signals we are connecting in our __init__:
self.status_proxy = bus.get_object(
'com.ubuntuone.SyncDaemon', '/status')
self.status_iface = dbus.Interface(self.status_proxy,
dbus_interface='com.ubuntuone.SyncDaemon.Status')
self.status_iface.connect_to_signal(
'StatusChanged', self.status_changed)
# Get the status as of right now
self._last_status = self.process_status(
self.status_proxy.current_status())
And what's status_changed?
def status_changed(self, status):
print "New status:", status
self._last_status = self.process_status(status)
self.update_menu()
The process_status function is boring code to convert the info from
syncdaemon's status into a human-readable thing like "Sync is up-to-date". So we
store that in self._last_status and update the menu.
What menu? Well, a QSystemTrayIcon's context menu! What you have read are the main pieces
you need to create something useful: a Ubuntu One tray app you can use in KDE, XFCE or openbox.
Or, if you are on unity and install sni-qt, a Ubuntu One app indicator!
Dr. Cooper, I hope this letter finds you in good health. I couldn't avoid overhearing you talk with Dr. Hofstadter on our building's staircase the other day.
Specifically when you mused about how life would be if people evolved from reptiles. I am so disappointed in you.
First, you mention that lizards are cold-blooded. Which is true. And that when it's cold they get slower. Which is also true. But then you sayd something like "the weatherlizard would say 'it's slow outside' instead of 'it's cold'".
POPPYCOCK Dr. Cooper! If the lizard is slow because it's cold, it would perceive everything out there as fast, not slow, just like slow cars see faster cars as, you know... fast?
Also, the mention about suggesting the lizard should wear a sweater is a slap on the face of physics. Sweaters are an insulator, not an energy source. What makes the inside of the sweater warm is the human, Dr. Cold-blooded lizards would have no such effect beyond the tiny thermal inertia such an imperfect wool insulator would allow.
If you are interested on further argument in human-like reptile civilization and folklore I will be happy to indulge, but I must say I expected better of you.
Mrs. Vartabedian.
PS: things like this are the reason why I never ask you to come home for cereal.
'Thinner,' the old Gypsy man with the rotting nose whispers...
—Richard Bachman (Stephen King)
I was not always fat. I used to be really, really thin. So thin that when I was
16, my motherforced me to eat. I was 1.75 and weighted about 65 kilos when I
finished highschool. So thin I used M sized shirts until I was 25.
That has, as those who know me can agree with, changed quite a bit. I broke
the 100kg bareer 10 years ago or so, and reached 123.5Kg earlier this week. Also,
I am not 3.5 meters tall, so I am close to having doubled my weight and body mass in these 25 years.
That is not a good thing. It's such a bad thing, that my doctor has explained to me
that if I don't lose a lot of weight soonish, I am going to fucking go and die. Not
next week, not next year, but not in 40 years either.
Since my weight started going up while I was in college and my dad was sick, I always
blamed some anxiety problem, eating crap, a sedentary lifestyle, the usual suspects.
So, I got some tests done. Turns out I was more or less right. The "more" is because
I do have to stop eating crap, and I need to exercise more. The "less" is because I
have (among other things) hyperinsulinemia. Wanna guess what are the most visible symptom
of that?
High blood pressure (on medication since 5 years ago)
Increased VLDL (diagnosed 2 years ago)
Lethargy (fuck yes)
Weight gain (double fuck yes)
And what does weight gain of this kind do to you? A lot of other bad things.
Since tuesday I am on a very specific diet, and taking a drug to reduce
my insulin production (Metformin). I am feeling active, and have lost 3
kilos in 4 days (yes, I know that is not a sustainable rate and will
plateau).
My feet stopped swelling.
I am not hungry all day.
I am walking 30 minutes, twice a day.
I want to code.
I feel good. I have felt better, when I started
taking BP meds, I have felt worse when my liver function decreased, I have felt
very bad, when my BP spiked, but good? I have not felt good in a very, very long
time.
This may not be the drug or the diet. Maybe it's placebo effect. Maybe it's something
else. On the other hand, I have decided that my life is too sweet to drop dead right
now. So, let's see how this goes.