APIs de Ubuntu One en Ejemplos (parte 1)
Así que acá va un pequeño tutorial acerca de como usar algunas de esas APIs. Lo hice usando Python y PyQt por varios motivos:
Son excelentes herramientas para prototipos
Tienen excelente soporte para las cosas que necesito (DBus, HTTP, OAuth)
Es lo que sé y me gusta. Lo hice un domingo, no lo pienso hacer en PHP y Gtk.
Dicho eso, no hay nada específico de python o de Qt en este código. Donde hago un request HTTP usando QtNetwork, podés usar libsoup o lo que fuere.
Vayamos a los bifes entonces. Las piezas más importantes de Ubuntu One, desde el punto de vista de infraestructura, son Ubuntu SSO Client, que se encarga de login, registración, etc, y SyncDaemos que maneja la sincronización de archivos.
Para interactuar con ellas, en Linux, ofrecen interfaces DBus. Así que, por ejemplo, este es un fragmento mostrando como obtener las credenciales de Ubuntu One (esto normalmente sería parte del __init__ de un objeto):
# 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 notaste que get_credentials no devuelve las credenciales. Lo que hace es, le dice a SyncDaemon que las obtenga, y entonces, si/cuando aparecen, se emite una de esas señales, y uno de los métodos conectados se llama. Esto está bueno porque no tenemos que preocuparnos de que se nos bloquee la aplicación mientras SyncDaemon está buscando las credenciales.
¿Y qué hay en esos métodos? ¡No mucho!
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ásicamente, self._credentials contiene unas credenciales, o None. Felicitaciones, ya entramos a Ubuntu One.
¡Hagamos algo útil! ¿Que tal preguntar cuánto espacio libre hay en la cuenta? Para eso, no podemos usar las APIs locales, si no conectarnos a los servers, que son los que saben si estás excedido de quota o no.
El acceso se controla via OAuth, por lo que para acceder a esa API necesitamos firmar nuestros pedidos. Aquí se ve como se hace. No es particularmente iluminador, yo no lo escribí, solamente 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 pedimos el estado de quota? Accediendo al punto de entrada https://one.ubuntu.com/api/quota/ con la autorización adecuada, se obtiene un diccionario JSON con el espacio total y el usado. Acá hay una muestra de como hacerlo:
# 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 nuevo: get_quota no devuelve la quota. Sólo lanza un pedido HTTP a los servers de Ubuntu One, que (eventualmente) responden con los datos. No querés que tu app se quede ahí trabada mientras tanto, por eso QNetworkAccessManager va a llamar a self.reply_finished cuando tenga la respuesta:
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 queremos? ¿Qué tal notificación cuando cambia el status de SyncDaemon? Por ejemplo, cuando la sincronización está al día, o cuando te desconecta. De nuevo, esas son señales DBus a las que uno se conecta en __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())
Y esta es status_changed:
def status_changed(self, status): print "New status:", status self._last_status = self.process_status(status) self.update_menu()
la función process_status is código aburrido para convertir la info de status de syncdaemon en una cosa legible como "Sync is up-to-date", así que guardamos eso en self._last_status y actualizamos el menú.
¿Qué menú? ¡Un menú contextual de un QSystemTrayIcon! Lo que leyeron son las piezas principales que se necesitan para crear algo útil: una aplicación de SystemTray para Ubuntu One que se puede usar en KDE, XFCE u Openbox. O, si estás en unity y tenés sni-qt instalado, un app indicator.
El código fuente del ejemplo completo está en mi proyecto u1-toys en launchpad y éste es el código fuente completo (excepto los iconos, bajense el repo, mejor)
Viniendo pronto (espero), más apps de ejemplo, y cosas copadas que se pueden hacer con nuestras APIs.