Ir al contenido principal

Ralsina.Me — El sitio web de Roberto Alsina

APIs de Ubuntu One en Ejemplos (parte 1)

Así que acá va un pe­que­ño tu­to­rial acer­ca de co­mo usar al­gu­nas de esas APIs. Lo hi­ce usan­do Py­thon y Py­Qt por va­rios mo­ti­vo­s:

  • Son ex­­ce­­len­­tes he­­rra­­mien­­tas pa­­ra pro­­­to­­­ti­­pos

  • Tie­­nen ex­­ce­­len­­te so­­­po­r­­te pa­­ra las co­­sas que ne­­ce­­si­­to (DBus, HTTP, OAu­­th)

  • Es lo que sé y me gus­­ta. Lo hi­­ce un do­­­mi­n­­go, no lo pien­­so ha­­cer en PHP y Gtk.

Di­cho eso, no hay na­da es­pe­cí­fi­co de py­thon o de Qt en es­te có­di­go. Don­de ha­go un re­quest HTTP usan­do QtNe­two­rk, po­dés usar lib­soup o lo que fue­re.

Va­ya­mos a los bi­fes en­ton­ce­s. Las pie­zas más im­por­tan­tes de Ubun­tu One, des­de el pun­to de vis­ta de in­fra­es­truc­tu­ra, son Ubun­tu SSO Clien­t, que se en­car­ga de lo­gi­n, re­gis­tra­ció­n, etc, y Syn­c­Dae­mos que ma­ne­ja la sin­cro­ni­za­ción de ar­chi­vo­s.

Pa­ra in­te­rac­tuar con ella­s, en Li­nu­x, ofre­cen in­ter­fa­ces DBus. Así que, por ejem­plo, es­te es un frag­men­to mos­tran­do co­mo ob­te­ner las cre­den­cia­les de Ubun­tu One (es­to nor­mal­men­te se­ría par­te del __i­ni­t__ de un ob­je­to­):

# 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()

Tal vez no­tas­te que ge­t_­cre­den­tials no de­vuel­ve las cre­den­cia­le­s. Lo que ha­ce es, le di­ce a Syn­c­Dae­mon que las ob­ten­ga, y en­ton­ce­s, si/­cuan­do apa­re­cen, se emi­te una de esas se­ña­le­s, y uno de los mé­to­dos co­nec­ta­dos se lla­ma. Es­to es­tá bue­no por­que no te­ne­mos que preo­cu­par­nos de que se nos blo­quee la apli­ca­ción mien­tras Syn­c­Dae­mon es­tá bus­can­do las cre­den­cia­le­s.

¿Y qué hay en esos mé­to­do­s? ¡No mu­cho!

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

Así que bá­si­ca­men­te, se­l­f._­cre­den­tials con­tie­ne unas cre­den­cia­le­s, o No­ne. Fe­li­ci­ta­cio­nes, ya en­tra­mos a Ubun­tu One.

¡Ha­ga­mos al­go úti­l! ¿Que tal pre­gun­tar cuán­to es­pa­cio li­bre hay en la cuen­ta? Pa­ra eso, no po­de­mos usar las APIs lo­ca­le­s, si no co­nec­tar­nos a los ser­ver­s, que son los que sa­ben si es­tás ex­ce­di­do de quo­ta o no.

El ac­ce­so se con­tro­la via OAu­th, por lo que pa­ra ac­ce­der a esa API ne­ce­si­ta­mos fir­mar nues­tros pe­di­do­s. Aquí se ve co­mo se ha­ce. No es par­ti­cu­lar­men­te ilu­mi­na­do­r, yo no lo es­cri­bí, so­la­men­te lo uso:

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

¿Y có­mo pe­di­mos el es­ta­do de quo­ta? Ac­ce­dien­do al pun­to de en­tra­da http­s://o­ne.u­bun­tu.­co­m/a­pi/­quo­ta/ con la au­to­ri­za­ción ade­cua­da, se ob­tie­ne un dic­cio­na­rio JSON con el es­pa­cio to­tal y el usa­do. Acá hay una mues­tra de co­mo ha­cer­lo:

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

De nue­vo: ge­t_­quo­ta no de­vuel­ve la quo­ta. Só­lo lan­za un pe­di­do HTTP a los ser­vers de Ubun­tu One, que (e­ven­tual­men­te) res­pon­den con los da­to­s. No que­rés que tu app se que­de ahí tra­ba­da mien­tras tan­to, por eso QNe­two­rkAc­ce­ss­Ma­na­ger va a lla­mar a se­l­f.­re­pl­y_­fi­nis­hed cuan­do ten­ga la res­pues­ta:

def reply_finished(self, reply):
    if unicode(reply.url().path()) == u'/api/quota/':
        # Handle quota responses
        self._quota_info = json.loads(unicode(reply.readAll()))
        print "Got quota: ", self._quota_info
        # Again, don't worry about update_menu yet ;-)
        self.update_menu()

¿Qué más que­re­mo­s? ¿Qué tal no­ti­fi­ca­ción cuan­do cam­bia el sta­tus de Syn­c­Dae­mo­n? Por ejem­plo, cuan­do la sin­cro­ni­za­ción es­tá al día, o cuan­do te des­co­nec­ta. De nue­vo, esas son se­ña­les DBus a las que uno se co­nec­ta en __i­ni­t__:

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

Y es­ta es sta­tus_­chan­ge­d:

def status_changed(self, status):
    print "New status:", status
    self._last_status = self.process_status(status)
    self.update_menu()

la fun­ción pro­ce­ss_s­ta­tus is có­di­go abu­rri­do pa­ra con­ver­tir la in­fo de sta­tus de syn­c­dae­mon en una co­sa le­gi­ble co­mo "S­ync is up-­to­-­da­te", así que guar­da­mos eso en se­l­f._­las­t_s­ta­tus y ac­tua­li­za­mos el me­nú.

¿Qué me­nú? ¡Un me­nú con­tex­tual de un QS­ys­te­m­Tra­yI­co­n! Lo que le­ye­ron son las pie­zas prin­ci­pa­les que se ne­ce­si­tan pa­ra crear al­go úti­l: una apli­ca­ción de Sys­te­m­Tray pa­ra Ubun­tu One que se pue­de usar en KDE, XFCE u Open­bo­x. O, si es­tás en uni­ty y te­nés sni-­qt ins­ta­la­do, un app in­di­ca­to­r.

http://ubuntuone.com/7iXTbysoMM9PIUS9Ai4TNn

El in­di­ca­dor en ac­ció­n.

El có­di­go fuen­te del ejem­plo com­ple­to es­tá en mi pro­yec­to u1-­to­ys en laun­ch­pad y és­te es el có­di­go fuen­te com­ple­to (ex­cep­to los ico­nos, ba­jen­se el re­po, me­jo­r)

Vi­nien­do pron­to (es­pe­ro­), más apps de ejem­plo, y co­sas co­pa­das que se pue­den ha­cer con nues­tras APIs.

Matěj Cepl / 2012-03-13 11:03:

I usually try hard not to comment on products by companies which compete with my employer (I work at Red Hat), but sheer wrongness of the sentences "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" compelled me to react. Do you intentionally ignore the fact that Ubuntu One (as a server) is completely proprietary or there is some reality distortion field in action here and you try to persuade yourself that you do something else than you do?

To make myself completely clear, I don't object to the fact that Canonical tries to make money for their open source products by selling proprietary service, that's their business decision, and if you make money with which you can provide Ubuntu, good for you. I am just offended by the depth of hypocrisy and misleading sleaze with which Canonical is not willing to admit it. It is not just about you, but many other cases (see infamous http://twit.tv/show/floss-w... which turned out to be an unpaid infomercial for the service).

Best,

Matěj Cepl (mcepl at redhat dot com ; sorry, Disqus is broken, so I was not able to login with my account)

Roberto Alsina / 2012-03-13 11:19:

I have no idea what that link you provide is (chromium won't let me open it, claims it's malware).

I will not try to answer to your comments in kind, because, really, I feel like you are trolling me, it's 8AM, and I am not interested. Sorry if you felt "compelled to react". That was hardly the point of the post. But really, hypocrisy and sleaze? That's what you get out of a 20 minute tutorial on accessing some APIs?

That the server side of Ubuntu One is not open source is known. Hey, if you ask in, say, askubuntu.com about how you can get your own Ubuntu One server someone (maybe even me) will explain to you that no, we are not sharing 
that, but there is a perfectly nice, clear and open protocol definition you can use if you want to.

But no, we are not sharing that, and yes, it's because Canonical hopes and wishes to make some money out of it. Does it mean I don't do free software? No, it doesn't. Does it mean I don't feel good writing it? No it doesn't. Did I say Canonical produces only open source software, no I didn't.

So, congratulations on taking a true statement, getting all worked up about it and spitting a little bile on the comment section of my personal blog. Hope that makes you feel all warm inside.

Matěj Cepl / 2012-03-13 12:15:

 > I have no idea what that link you provide is (chromium won't let me open it, claims it's malware).
An episode of FLOSS Weekly podcast where Jono Bacon and Stuart Langridge (both of Canonical) presented Ubuntu One ignoring to mention (notice once again the name of the show) that the server is proprietary. If that is labeled by Chromium as a malware, one more reason why not to use Chromium ;).

Matěj

Roberto Alsina / 2012-03-13 12:55:

I suggest you go troll an Evolution developer. I heard they support proprietary GMail, and never say that GMail is not Free Software, so they are not real free software people.

Shulai / 2012-03-13 22:21:

I almost asked you to post an OwnCloud version, but I guess you won't take it as a joke, but rather sending me to do a gynecological test to my sister. :-D

BTW, what happened with the "RMS hates me" t-shirts? (Sorry, I can't resist!)

Roberto Alsina / 2012-03-13 22:57:

I am thinking about doing a "Yes, I am a freedom-hating argentinian" ones. I would be happy to do a OwnCLoud version, but... well, I have no idea about their APIs. Have any pointers?

Shulai / 2012-03-15 00:51:

I just realized comments are shared between English and Spanish blog posts, so there is no real reason to write in English, painfully trying to put my ideas in the right words. What a relief!

La verdad, "la nube" no me impresiona gran cosa, y OwnCloud no fue la excepción, así que no llegué a ver sobre como instalarlo, y menos nada que tenga que ver con código.

PhoenixRevived / 2012-04-26 20:47:

Do you have a brief tutorial on how I can do this using Qt/C++ since I can't use python?

Roberto Alsina / 2012-04-26 20:50:

The only part that is not a straighforward translation is the OAUTH bits. Is there any C++ Oauth library you recommend? If so, I will be happy to rewrite this in C++.

PhoenixRevived / 2012-04-26 21:31:

I have found KQOauth to be ideal for oauth authentication from ubuntu (works for Twitter, etc.): 

http://www.johanpaul.com/bl...

Thank you for offering to rewrite the tutorial in C++/Qt. Greatly appreciated!

PhoenixRevived / 2012-04-26 23:40:

Hi Roberto,
I had a related question: In your example above, you are using an undocumented API called 'quota'. Is there someplace I can get a list of all the undocumented APIs? Basically, here is what I am trying to do in my c++ app:

1. Allow a user to log in to their u1 account
2. Get a list of folders that they have published to the cloud
3. For each folder, get a list of files they have published t0 the cloud
4. For each file, get the public-URL to read the file from the cloud

Since I will be running this on an embedded device with limited storage, I don't want to sync with the cloud. Instead, I want to directly read the file from the public-URL

I would greatly appreciate if your example tutorial in Qt/C++ could show me how to do this. Huge thanks for your help!!

Roberto Alsina / 2012-04-27 00:38:

We are not supposed to have undocumented APIs :-)

I will check tomorrow to see if it's documented, and if not, will get it documented. Your use case should be doable using only the REST API, since you will not be using SSO client or syncdaemon, so it's going to look quite different from this example.

PhoenixRevived / 2012-04-27 03:57:

Thank you so much! Looking forward to it!

sil / 2012-04-27 08:29:

Hey there! As ralsina says, all our public APIs should be documented (at https://one.ubuntu.com/deve... as you likely already know). The quota API isn't yet documented; good catch, there! I let that slip through my fingers :)

On doing what you want to do, you should be able to do that by getting an OAuth token (either by asking for username and password (https://one.ubuntu.com/deve... or by opening a browser and using the OAuth 1.0 dance (https://one.ubuntu.com/deve..., and then querying the Files API (https://one.ubuntu.com/deve... to get a list of synced folders and the folders and files within, or better still by hitting https://one.ubuntu.com/api/... to get the list of public files.

PhoenixRevived / 2012-04-27 15:18:

Thanks for the response - very quick! I will try these and get back to you.

PhoenixRevived / 2012-04-27 17:26:

Hi sil, I am afraid I was not able to get very far with the links you provided. Would it be possible for you to show some example REST requests for my use case? Your help is greatly appreciated!

John Lenton / 2012-04-27 09:07:

One nitpick: instead of doing `_, _, _, _, query, _ = urlparse(uri)`, just do `query = urlparse(uri).query` :)

PhoenixRevived / 2012-08-10 19:43:

I am scratching my head with the Ubuntu One files API.

1. I performed an oAuth authentication dance and got a token.2. I used the following URL with an authenticated GET to fetch the volumes list, which it did successfully: https://one.ubuntu.com/api/.... I got the following bacK: [{"resource_path": "/volumes/~/Ubuntu One", "when_created": "2012-06-18T19:05:13Z", "generation": 5, "path": "~/Ubuntu One", "content_path": "/content/~/Ubuntu One", "type": "root", "node_path": "/~/Ubuntu One"}]4. I then tried to get the next level from the root "https://one.ubuntu.com/api/... One"This returns nothing although there are folders below with files in them.
What am I doing wrong?


Contents © 2000-2020 Roberto Alsina