The Dig (Matt Turner Series)
Review:a page turner. Fun book with an interesting premise. |
Review:a page turner. Fun book with an interesting premise. |
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:
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.
This post is a joke.
Dr. Cooper, I hope this letter fids you in good health. I couldn't avoid overhearing you talk with Dr. Hofstadter on our building's staircase the other day.
I am speaking about 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.
-- Richard Bachman (Stephen King)
No siempre fuí gordo. Yo solía ser muy muy flaco. Tan flaco que mi vieja me obligaba a comer. Medía 1.75 y pesaba tipo 65 kilos cuando terminé el secundario. Tan flaco que a los 25 todavía usaba remeras talle M.
Cualquiera que me conozca sabe que ya no es así. Pasé los 100Kg hace 10 años, y el lunes pasado pesaba 123.5Kg. Dado que no mido 3.5 metros de alto, es justo decir que dupliqué mi ancho (mi diámetro? Mi sección!) en estos 25 años.
Eso no es bueno. Es tan no bueno, que mi doctor me ha explicado amablemente que si no bajo mucho de peso pronto, me voy a cagar muriendo. No la semana que viene, no el año que viene, pero tampoco dentro de 40 años.
Como empecé a subir de peso cuando estaba en la facultad y mi viejo se enfermó, siempre culpé a la ansiedad, a comer basura, a una vida sedentaria, lo que hacen todos.
Así que me hice estudios. Resulta que era más o menos cierto. Más porque sí, tengo que dejar de comer basura, y hacer más ejercicio. Pero menos, porque tengo (entre otras cosas) hiperinsulinemia. ¿Saben cuáles son los síntomas mas comunes?
Hipertensión (medicado hace 5 años)
Aumento de VLDL (diagnosticado hace 2 años)
Letargo (mierda que sí)
Aumento de peso (doble mierda que sí)
¿Y qué te hace esta clase de aumento de peso? Un montón de otras cosas malas.
Desde el martes estoy con una dieta muy específica, y tomando Metformina para reducir mi nivel de insulina. Me siento activo, y bajé 3 kilos en 4 días (sí, ya sé que no es sostenible, y que no voy a bajar siempre así).
No se me hinchan más los pies.
No tengo hambre todo el día.
Camino 30 minutos, dos veces al día.
Quiero programar.
Me siento bien. Otras veces, cuando me cambiaban medicación de la presión me he sentido mejor, cuando tuve ataques me he sentido peor, pero bien? No me había sentido bien en mucho, mucho tiempo.
Tal vez no sea la droga ni la dieta sino el efecto placebo. Capaz que es otra cosa. Por otro lado, creo que me está yendo demasiado bien como para ser tan boludo de morirme ahora. Veamos como sale.