Best Chrome extensions

Since I haven’t been blogging for a while, here is a list of some Chrome extensions you can’t miss.

Sanity

  • AdBlock Plus – I’m not sure how anyone can use a web browser without AdBlock. It’ll make sure you don’t see ads, anywhere.

Productivity

  • Pocket – If you have a tablet, this is the extension you need. Save interesting reads so you can access them later from your computer, smartphone or tablet. It also nicely extracts the text from the page, leaving it free of clutter.
  • Google Tasks – Who doesn’t want to be able to save TODO items just by typing “t whatever” into the omnibox? I haven’t tried it, but some may prefer Better Google Tasks.
  • Rapportive – Adds a sidebar with information from Linkedin, Twitter, etc. on the sender. Great when receiving e-mails from people you don’t know, or for keeping up to date.
  • Google Dictionary – Lets you double-click on any word to look up its definition.

Privacy

  • HTTPS Everywhere – Protect your privacy when using insecure networks. This extension forces many popular sites to always encrypt communications.
  • DoNotTrackMe – If you’re using AdBlock you won’t see ads, so what does it matter whether they are customized to your interests? Use this extension to stop ad networks from tracking you.
  • Extra: Facebook Link Rewriter, Unsocial Reader for Facebook.

Social

  • Rapportive – For GMail users, this extension adds a sidebar with information about who you’re talking to (LinkedIn profile, etc).
  • Google +1 Button – Add a “+1″ button to Chrome.

What extensions do you use? Let me know about your favorite one.

A gentle introduction to Zeitgeist’s Python API

In this post I’ll make a quick introduction by example on how to use Zeitgeist’s Python API for good and profit.

If you’re interested in using Zeitgeist from C instead, see the libzeitgeist examples; to use it with C++/Qt, Trever’s «a web browser in 4 steps» may be of interest.

First things first

In case you’re not familiar with Zeitgeist, it may prove helpful to first read Mikkel’s introduction to Zeitgeist post.

If that’s too much to read, just should know that Zeitgeist is an event log. Like the history in your browser, it keeps track of what websites you open at which point in time. It also keeps track of when you close them, and of what browser you used, since it’s a system-wide service. Furthermore, it does the same for files, conversations, e-mails, and anything else you want to insert into it.

So Zeitgeist is a database of events, and an event can be pretty much anything. But what does it look like? It’s main attributes are the following:

  • timestamp – when did the event happen (milliseconds since Unix epoch)
  • interpretation – what sort of event is it (eg. opened, closed)
  • manifestation – why did it happen (user activity, notification…)
  • actor – which is the primary application involved
  • origin – where did it come from (eg. website where you clicked the link that opened this page)

Additionally, each event has one or more subjects, which have the following attributes:

  • uri
  • current_uri – updated URI if it changed since the event
  • interpretation – abstract type (document, image, video…)
  • manifestation – how it is stored (file, remote object, website)
  • origin – parent folder for files, domain name for websites
  • mimetype
  • text – a title for the event (eg. filename, website title…)
  • storage – identifier for the storage medium of the subject (eg. local, online, pendrive X)

Retrieving recent data

Okay, so let’s say you want to know the last song you’ve listened to (if you have a Zeitgeist-enabled music player). It’s a simple as:

from zeitgeist.client import ZeitgeistClient
from zeitgeist.datamodel import *
 
zeitgeist = ZeitgeistClient()
 
def on_events_received(events):
    if events:
        song = events[0]
        print "Last song: %s" % song.subjects[0].uri
    else:
        print "You haven't listened to any songs."
 
template = Event.new_for_values(subject_interpretation=Interpretation.AUDIO)
zeitgeist.find_events_for_template(template, on_events_received, num_events=1)
 
# Start a mainloop - note: the Qt mainloop also works
from gi.repository import GLib
GLib.MainLoop().run()

This may need some explaining. We start by importing the Zeitgeist client and some associated data structures (such as Event and Interpretation), and create an instance of the client. The ZeitgeistClient class is a wrapper around Zeitgeist’s D-Bus API which not only makes it much nicer to use, but also makes it easy to install monitors (as we will see later) and provides convenient functionality such as automatic reconnection if the connection to the engine is lost.

To query for the most recent song, we just need to create a template with the restrictions we want to impose and submit it to Zeitgeist using the find_events_for_template call. If you haven’t read Mikkel’s post yet, please do so, as it introduces the structure of events and subjects (in short: an event has a timestamp, some other properties, and one or more subjects representing the resources -files, websites, people…- involved in the event).

The Python API is inherently asynchronous (if for some reason you need a synchronous API, you may still use the lower level ZeitgeistDBusInterface class), so we need to define a callback function to handle the results we receive from the Zeitgeist engine.

Finally, we need to create a main loop so the asynchronous functions can run.


Now this was a pretty simple example. Let’s make it more interesting. One song isn’t much, so let’s get the 5 most recent songs. Also, now we want both songs and videos.

The first part is pretty easy, we just need to change the num_events parameter. For the second extension, we have to change the event template. In fact, now we need two different event templates and the find_events_for_templates function, which takes an arbitrary number of event templates and ORs them. The result is as follows:

from zeitgeist.client import ZeitgeistClient
from zeitgeist.datamodel import *
 
zeitgeist = ZeitgeistClient()
 
def on_events_received(events):
    for event in events:
        print "- %s" % event.subjects[0].uri
 
tmpl1 = Event.new_for_values(subject_interpretation=Interpretation.AUDIO)
tmpl2 = Event.new_for_values(subject_interpretation=Interpretation.VIDEO)
zeitgeist.find_events_for_templates([tmpl1, tmpl2],
                                    on_events_received, num_events=5)
 
# Start a mainloop
from gi.repository import GLib
GLib.MainLoop().run()

This will work, but unless you’re lucky you’re likely to get some duplicate line. Why is this? Well, other than that you may have used the same file twice, don’t forget that what you are requesting are actually events. If you’ve started playing a given song, you probably also stopped playing it, so that’s actually two of them (an AccessEvent and a LeaveEvent). Since this isn’t what we want, we’ll change the query a bit:

zeitgeist.find_events_for_templates(
    [tmpl1, tmpl2],
    on_events_received,
    num_events=5,
    result_type=ResultType.MostRecentSubjects,
    storage_state=StorageState.Available)

By requesting the most recent subjects, vs. the most recent events, we can filter out events with duplicate URI. See the ResultType documentation for other modes you can use. Note particularly the MostPopularSubjects result type.

I also used the chance to introduce the storage_state parameter. This one will filter out events for files Zeitgeist knows aren’t available (this mostly means online resources won’t be shown if you don’t have a network connnection; there’s also support for handling external storage media, but because of problems with GIO this is currently disabled).

Last but not least, the find_events_for_* methods also accept a timerange parameter. It defaults to TimeRange.until_now(), but you may change it to TimeRange.always() (if for some reason you’re working with events in the future) or to any other time range of your choice. Here it’s important to note that Zeitgeist’s timestamps use millisecond precision.


For more advanced queries, you can use more complex combinations of events and subject templates. The rule to keep in mind here is that events are OR’d and subjects are ANDed.

Additionally, some field (actor, origin, mimetype, uri and current_uri) may be prefixed with an exclamation mark (“!”) for NOT, or you may append an asterisc (“*”) to them for prefix search. You can even combine the two operators together. Here’s an example of a template you could build:

subj1 = Subject.new_for_values(interpretation=Interpretation.SOURCE_CODE,
                               uri="file:///home/rainct/Development/*")
subj2 = Subject.new_for_values(uri="!file:///home/rainct/Development/zeitgeist/*")
tmpl1 = Event.new_for_values(interpretation=Interpretation.MODIFY_EVENT,
                             subjects=[subj1, subj2])
templates = [tmpl1]

In my case, this template would fetch a list of the source code files I modified most recently but excluding those related to the Zeitgeist project.

Working with big sets of data

In case you’re trying to do something crazy, you may end up with a Zeitgeist query complaining that it exceeded the memory limit. You’re not supposed to do that. Instead, we provide some methods for working with large collections of events.

from zeitgeist.client import ZeitgeistClient
from zeitgeist.datamodel import *
 
zeitgeist = ZeitgeistClient()
 
def on_events_received(events):
    for event in events:
        print '- %s' % event.subjects[0].uri
 
def on_ids_received(event_ids):
    print 'A total of %d source code files were found.' % len(event_ids)
    print 'Fetching the first 100...'
    zeitgeist.get_events(event_ids[:100], on_events_received)
 
tmpl = Event.new_for_values(subject_interpretation=Interpretation.SOURCE_CODE)
zeitgeist.find_event_ids_for_templates(
    [tmpl],
    on_ids_received,
    num_events=10000, # you can use 0 for "all events", but do you really need to?
    timerange=TimeRange.from_seconds_ago(3600*24*30*3),
    result_type=ResultType.MostPopularSubjects)
 
# Start a mainloop
from gi.repository import GLib
GLib.MainLoop().run()

And there you have the source code files you worked with during the last 3 months, ordered from most to least popular (popularity is measured counting the number of events; for more precision, maybe you could limit the results to events with interpretation AccessEvent).

Why do we provide this mechanism instead of querying with a simple offset? Well, this avoids problems when the log changes (events are inserted or deleted). Have you ever been exploring the latest posts in some website, and as you change to the next page some of the results from the previous page show up again (because new posts have been added in the meantime)? With Zeitgeist this won’t happen.

Receiving information in real time

At this point you’re an expert at requesting all sorts of data from Zeitgeist, but now you want to show a list of the last kitten images you’ve viewed, updated in real time. Don’t worry, Zeitgeist can provide for this:

from zeitgeist.client import ZeitgeistClient
from zeitgeist.datamodel import *
 
zeitgeist = ZeitgeistClient()
 
def on_insert(time_range, events):
    # do awesome stuff with the events here
    print events
 
def on_delete(time_range, event_ids):
    # a previously inserted event was deleted
    print event_ids
 
templates = [Event.new_for_values(subject_uri='file:///home/user/kittens/*',
                                  subject_interpretation=Interpretation.IMAGE)]
zeitgeist.install_monitor(TimeRange.always(), templates, on_insert, on_delete)
 
# Start a mainloop
from gi.repository import GLib
GLib.MainLoop().run()

It’s important to note that on_delete won’t be called when an image is deleted (that’d be a newly inserted event with interpretation=DELETE_EVENT); rather, it’s called when a previously inserted event is deleted (for example, using the “forget recent history» option in Activity Log Manager).

In case you’re curious: for best performance, this doesn’t actually use D-Bus signals. Instead, this little call will setup a D-Bus object behind the scenes and register it with the Zeitgeist engine, so it can notify said object when (and only when) an event of its interest is registered.

To stop receiving notifications for a template, you’ll need the save the object returned by the install_monitor call:

m = zeitgeist.install_monitor(TimeRange.always(), templates, on_insert, on_delete)
zeitgeist.remove_monitor(m)

Pro Tip: You can use the Zeitgeist Explorer GUI to quickly try out different queries (note: it’s still work in progress, so much funcionality is missing, but it does work somewhat).

Contextual awesomeness: finding related events

By now you’re familiar with retrieving events and keeping them up to date. Now it’s time for a little secret:

import time
from zeitgeist.client import ZeitgeistClient
from zeitgeist.datamodel import *
 
zeitgeist = ZeitgeistClient()
 
def on_related_received(uris):
    print 'Related URIs:'
    for uri in uris:
        print ' - %s' % uri
 
query_templates = [Event.new_for_values(
    subject_interpretation=Interpretation.SOURCE_CODE,
    subject_uri='file:///home/rainct/Development/zeitgeist/*',
    subject_mimetype="text/x-vala")]
 
result_templates = [Event.new_for_values(
    subject_interpretation=Interpretation.WEBSITE,
    subject_manifestation=Manifestation.WEB_DATA_OBJECT)]
 
now = time.time()*1000
zeitgeist.find_related_uris_for_events(
    query_templates,
    on_related_received,
    time_range=TimeRange(now - 1000*3600*24*30*6, now),
    result_event_templates=result_templates,
    num_events=10)
 
# Start a mainloop
from gi.repository import GLib
GLib.MainLoop().run()

This little query example will return up to 10 websites I used at the same time as the Vala files inside my Zeitgeist directory, considering only data from the last 6 months. Nice, huh?

This is an experimental feature, and it doesn’t work well when operating on big inputs, so it’s usually better to use the find_related_uris_for_uris variant (which replaces the first query_templates parameter with a list of URIs).

Advanced searching: the FTS extension

Some people think prefix searches aren’t good enough for them, and this is why the Zeitgeist engine ships by default with a FTS (Full Text Search) extension.

Using the methods provided by this extension you can perform more advanced queries against subjects’ current_uri and text properties (unlike the name may suggest, the FTS extension doesn’t index the content of the files, but just the information in the event).

This is exposed as zeitgeist_index_search in libzeitgeist (the C library), but unfortunately isn’t currently available in the Python API. If you still need it, you’ll have to fallback to pretty much using the D-Bus interface (you still get reconnection support, though). Here’s an example:

from zeitgeist.client import ZeitgeistClient
from zeitgeist.datamodel import *
 
zeitgeist = ZeitgeistClient()
index = zeitgeist._iface.get_extension('Index', 'index/activity')
 
query            = 'hello' # search query
time_range       = TimeRange.always()
event_templates  = []
offset           = 0
num_events       = 10
result_type      = 100 # magic number for "relevancy" (ResultType.* also work)
 
def on_reply(events, num_estimated_matches):
    print 'Got %d out of ~%d results.' % (len(events), num_estimated_matches)
    events = map(Event, events)
    for event in events:
        print ' - %s' % event.subjects[0].uri
 
def on_error(exception):
    print 'Error from FTS:', exception
 
index.Search(query, time_range, event_templates,
             offset, num_events, result_type,
             reply_handler=on_reply, error_handler=on_error)
 
# Start a mainloop
from gi.repository import GLib
GLib.MainLoop().run()

The most interesting thing here is the query parameter. Quoting from the C documentation:

The default boolean operator is AND. Thus the query foo bar will
be interpreted as foo AND bar. To exclude a term from the result
set prepend it with a minus sign - eg foo -bar. Phrase queries
can be done by double quoting the string "foo is a bar". You can
truncate terms by appending a *.

There are a few keys you can prefix to a term or phrase to search
within a specific set of metadata. They are used like key:value.
The keys name and title search strictly within the text field of
the event subjects. The key app searches within the application
name or description that is found in the actor attribute of the
events. Lastly you can use the site key to search within the
domain name of the subject URIs.

You can also control the results with the boolean operators AND
and OR and you may use brackets, ( and ), to control the operator
precedence.

Modifying the log

So far we’ve only queried Zeitgeist for information, let’s get a bit more active.

You can delete events from Zeitgeist with the following query:

from zeitgeist.client import ZeitgeistClient
 
zeitgeist = ZeitgeistClient()
 
def on_deleted(timerange):
    print 'Deleted events going from %s to %s' % (timerange[0], timerange[1])
 
event_ids = [50] # put the IDs of the events you want to delete here
 
zeitgeist.delete_events(event_ids, on_deleted)
 
# Start a mainloop
from gi.repository import GLib
GLib.MainLoop().run()

The confirmation callback will receive a timerange going from the first to the last event. If no events were deleted (because they didn’t exist), you’ll get (-1, -1).


And now for the interesting part. If your application involves resources (files, websites, contacts, etc.) of any sort, you’ll probably want to let Zeitgeist know that you’re using them. It’s time that you write a data-source!

We start by registering the data-source. Here we go:

import time
from gi.repository import GLib
from zeitgeist.client import ZeitgeistClient
from zeitgeist.datamodel import *
 
zeitgeist = ZeitgeistClient()
 
def on_status_changed_callback(enabled):
    """ This method will be called whenever someone enables or disables
        the data-source. """
    if enabled:
        print 'Data-source enabled and ready to send events!'
    else:
        print 'Data-source disabled; don\'t send event, they\'ll be ignored.'
 
def register():
    # Always use the same unique_id. Name and description can change
    # freely.
    unique_id = 'com.example.your.data.source'
    name = 'user visible name (may be translated)'
    description = 'user visible description (may be translated)'
 
    # Describe what sort of events will be inserted (optional)
    subject_template = Subject()
    subject_template.interpretation = Interpretation.PLAIN_TEXT_DOCUMENT
    subject_template.manifestation = Manifestation.FILE_DATA_OBJECT
    templates = []
    for interp in (Interpretation.ACCESS_EVENT, Interpretation.LEAVE_EVENT):
        event_template = Event()
        event_template.interpretation = interp
        event_template.manifestation = Manifestation.USER_ACTIVITY
        event_template.append_subject(subject_template)
        templates.append(event_template)
 
    zeitgeist.register_data_source(unique_id, name, description, templates,
                                   on_status_changed_callback)

Once that’s done (and if it is enabled), we are free to send our events:

def log(title, uri, opened):
    subject = Subject.new_for_values(
        uri=uri,
        interpretation=Interpretation.PLAIN_TEXT_DOCUMENT,
        manifestation=Manifestation.FILE_DATA_OBJECT,
        origin=GLib.path_get_dirname(uri),
        mimetype='text/plain',
        text=title)
    event = Event.new_for_values(
        timestamp=time.time()*1000,
        manifestation=Manifestation.USER_ACTIVITY,
        actor='application://your_application_name.desktop',
        subjects=[subject])
    if opened:
        event.interpretation = Interpretation.ACCESS_EVENT
    else:
        event.interpretation = Interpretation.LEAVE_EVENT
 
    def on_id_received(event_ids):
        print 'Logged %s (%d) with event id %d.' % (title, opened, event_ids[0])
 
    zeitgeist.insert_events([event], on_id_received)
 
if __name__ == '__main__':
    register()
 
    log('test.txt', 'file:///tmp/test.txt', opened=True)
    log('another_file.txt', 'file:///tmp/another_file.txt', opened=True)
    log('another_file.txt', 'file:///tmp/another_file.txt', opened=False)
    log('test.txt', 'file:///tmp/test.txt', opened=False)
 
    # Start a mainloop
    GLib.MainLoop().run()

If you don’t know what interpretation and manifestation your subject should have, you can use the following utility methods:

from zeitgeist.mimetypes import *
 
print get_interpretation_for_mimetype('text/plain')
print get_manifestation_for_uri('file:///tmp/test.txt')

Event better, with Zeitgeist 0.9 you can just leave the subject (but not event!) interpretation and manifestation fields empty, and they’ll be guessed the same way as if you used those utility methods.

Pro Tip: You can examine all registered data-sources and toggle whether they are enabled or not using the zeitgeist-data-sources-gtk.py tool.

Conclusion

Wow, I’m impressed if you’ve got this far. By now you should have quite a good idea on how to use the Zeitgeist API, and I’m looking forward to seeing what you do with it in your next awesome project.

If you have any problem with Zeitgeist, feel free to visit us on IRC (#zeitgeist on irc.freenode.net), or join our mailing list. We’ll also be at GUADEC next week, so if you’re there make sure to say hi!

In case you missed them, here are some useful links:

Breu introducció al sistema de control de versions Subversion (SVN)

Us deixo aquí una breu introducció al Subversion (SVN), un sistema de control de versions (VCS) per al desenvolupament cooperatiu d’aplicacions informàtiques.

El Subversion permet únicament una forma de treballar centralitzada i no el trobo gaire agradable d’utilitzar, de manera que si tens l’opció et recomano que utilitzis un sistema més modern com ara el Bazaar (del qual ja en vaig parlar en un apunt anterior: Breu introducció al Bazaar) o el Git.

Ara bé, com que sovint no es té l’elecció (per exemple a l’incorporar-se en un projecte existent), aquí va el tutorial. Els conceptes que hi explico són molt bàsic, de manera que de fet es poden aplicar amb petits canvis a qualsevol altre sistema VCS.

eSpeak GUI 0.4 is out!

A few of you may remember espeak-gui, a graphical user interface for eSpeak I did release two years ago. While I haven’t been dedicating much time to it, it’s still alive and I’m happy to announce its fourth release!

This new release introduced automatic language guessing, a feature I’ve wanted for a while, as well as spell checking (contributed by Joe Burmeister).

Version 0.3, released a year ago and which I didn’t announce here, did also introduce Undo/Redo, pausing, file history logging, a bunch of translations and some voice options. It’s sister project, python-espeak, did also get some bug fixes since its first release.

As usual, packages for both espeak-gui and python-espeak are available in my PPA, and since last month in Debian unstable. Make sure you also install libtextcat0 (version 2.2-8 or newer) to get the language identification support.

Debian Games Team Meeting

This announcement was provided by Martin Erik Werner. I’m reproducing it for Planet Ubuntu.

The Debian/Ubuntu Games Team is organizing another meeting. If you’re into developing and/or packaging of games, or just generally curious about games in Debian/Ubuntu, you should join!

It will be held next Saturday, the 26th of November, in the #debian-games channel on irc.debian.org (also know as irc.oftc.net) at 10:00 UTC. More information is available on the wiki page Games/Meetings/2011-11-26.

The agenda starts off with the usual round of introductions, so if you’re new to the Team, say hi! Then we’ll be going through the action items from the last meeting, including work on the Debian Games LiveCD, and what’s up with the /usr/games/ path anyways?

Next we’ll be moving onto how the Games Team is faring in terms of members: are new recruits finding it comfortable, should we advertise more?

Next up it’s the squeeky penguin: Wheezy is somewhere in the not-completely-distant future, how does that affect the Games Team, should we be scuffling to get specific tasks done?

Then onto the recurring question of Sponsoring, and how to improve it, should we be utilising DebExpo more? What about our favourite PET?

Lastly, PlayDeb is doing some really neat stuff, would it make sense for our team to push some changes to PlayDeb? Would it make sense for PlayDeb to push changes to Debian Games?

Hopes are for a good discussion, and a merry time, hope to see you all there!