diff options
24 files changed, 986 insertions, 618 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 9137279..375f301 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6,7 +6,7 @@ Manish Sinha <manishsinha@ubuntu.com> Please thank our Contributers: * Travis Glenn Hansen - thansen * Jon Nettleton - jnettlet -* Mark Jtully - teester +* Mark Tully - teester * Tassilo Horn - tsdh * Patrick M. Niedzielski * Danielle Madeley diff --git a/chrome/zeitgeist.js b/chrome/zeitgeist.js index 20bc584..82bcb01 100644 --- a/chrome/zeitgeist.js +++ b/chrome/zeitgeist.js @@ -1,5 +1,7 @@ var plugin = document.embeds[0]; var tabInfo = {}; +var tabIdTimeouts = {}; +var currentTabs = {}; function onTabCreated (tab) { chrome.tabs.executeScript(tab.id, {file: "content_script.js"}); @@ -11,7 +13,11 @@ function onTabRemoved (tabid) { function onTabUpdated (tabid, changeInfo, tab) { if (!changeInfo.url) return; - chrome.tabs.executeScript(tabid, {file: "content_script.js"}); + window.clearTimeout(tabIdTimeouts[tabid]) + tabIdTimeouts[tabid] = window.setTimeout(function(){ + console.log("sending event for " + tab.url); + chrome.tabs.executeScript(tabid, {file: "content_script.js"});}, + 5000); } function onBookmarkCreated (bookmarkid, bookmark) { @@ -34,10 +40,14 @@ function sendAccessEvent (documentInfo, tabid) { } var mimetype = documentInfo.mimeType; var title = documentInfo.title; - plugin.insertEvent(url, - domain, - mimetype ? mimetype : "text/html", - title); + plugin.insertEvent(url, + domain, + mimetype ? mimetype : "text/html", + title); + console.log("save thumbnail for "+currentTabs[tabid]+": "+url); + chrome.tabs.captureVisibleTab(currentTabs[tabid], {format:"jpeg", quality:5}, function(dataUrl) { + plugin.saveSnapshot(url, dataUrl); + }); documentInfo.sentAccess = true; tabInfo[tabid] = documentInfo; @@ -79,12 +89,27 @@ if (!is_chromium) plugin.setActor("application://google-chrome.desktop"); else plugin.setActor("application://chromium-browser.desktop"); chrome.extension.onRequest.addListener (onExtensionRequest); -chrome.bookmarks.onCreated.addListener (onBookmarkCreated); +//chrome.bookmarks.onCreated.addListener (onBookmarkCreated); chrome.tabs.onUpdated.addListener (onTabUpdated); -chrome.tabs.onCreated.addListener (onTabCreated); -chrome.tabs.onRemoved.addListener (onTabRemoved); +chrome.tabs.onCreated.addListener( + function (tab) { + currentTabs[tab.id] = tab.windowId; + } +); + +chrome.tabs.onRemoved.addListener( + function (tabid) { + delete currentTabs[tabid]; + } +); +//chrome.tabs.onCreated.addListener (onTabCreated); +//chrome.tabs.onRemoved.addListener (onTabRemoved); -chrome.tabs.getAllInWindow(null, function (tabs) { - for (var i=0; i<tabs.length; i++) - chrome.tabs.executeScript(tabs[i].id, {file: "content_script.js"}); +chrome.windows.getAll({"populate" : true}, function (windows) { + for (var i = 0; i < windows.length; i++) { + var tabs = windows[i].tabs; + for (var j = 0; j < tabs.length; j++) { + chrome.tabs.executeScript(tabs[j].id, {file: "content_script.js"}); + } + } }); diff --git a/configure.ac b/configure.ac index e03ce5d..ed39916 100644 --- a/configure.ac +++ b/configure.ac @@ -63,7 +63,7 @@ AC_ARG_ENABLE([all-plugins], AC_MSG_NOTICE([Requested to enable all plugins: ${all_plugins}]) # The full list of plugins -allowed_plugins="bzr chrome eog geany gedit vim emacs tomboy telepathy xchat rhythmbox firefox totem-libzg firefox-36-libzg monodevelop" +allowed_plugins="bzr chrome eog geany gedit vim emacs tomboy telepathy xchat rhythmbox firefox totem-libzg firefox-36-libzg monodevelop thunderbird" # currently disabled = "epiphany" # npapi-plugin has a template Makefile.am, but don't use it directly @@ -154,10 +154,10 @@ for plugin in ${used_plugins}; do plugin_error_or_ignore "libzeitgeist not found" continue fi - if test "x$HAVE_GTK" = "xno"; then + if test "x$HAVE_GTK" = "xno"; then plugin_error_or_ignore "You need to install gtk development headers" continue - fi + fi PKG_CHECK_MODULES(GEANY, geany, [HAVE_GEANY=yes], [HAVE_GEANY=no]) if test "${HAVE_GEANY}" != "yes" ; then @@ -204,7 +204,7 @@ for plugin in ${used_plugins}; do PKG_CHECK_MODULES(ZEITGEIST_SHARP, zeitgeist-sharp, ENABLE_ZEITGEIST_SHARP=yes, ENABLE_ZEITGEIST_SHARP=no) PKG_CHECK_MODULES(TOMBOY_ADDINS, tomboy-addins, - HAS_TOMBOY_ADDINS=yes, HAS_TOMBOY_ADDINS=no) + HAS_TOMBOY_ADDINS=yes, HAS_TOMBOY_ADDINS=no) PKG_CHECK_MODULES(GTK_SHARP, gtk-sharp-2.0, HAS_GTK_SHARP=yes, HAS_GTK_SHARP=no) @@ -228,6 +228,8 @@ for plugin in ${used_plugins}; do fi AC_SUBST(ZEITGEIST_SHARP_LIBS) ;; + thunderbird) + ;; totem*) if test "${with_vala}" != "yes" ; then plugin_error_or_ignore "you need vala installed to use the ${plugin} plugin" @@ -265,7 +267,7 @@ for plugin in ${used_plugins}; do plugin_error_or_ignore "${plugin} is not handled" continue ;; - esac + esac # Add the specified plugin used_plugins2="${used_plugins2} ${plugin}" @@ -311,13 +313,13 @@ if test "x${PLUGINS}" != "x" ; then continue 2 ;; firefox) - AC_CONFIG_FILES([ - firefox/Makefile - firefox/extension/Makefile - ]) - continue 2 - ;; - firefox-36-libzg) + AC_CONFIG_FILES([ + firefox/Makefile + firefox/extension/Makefile + ]) + continue 2 + ;; + firefox-36-libzg) AC_CONFIG_FILES([ firefox-36-libzg/Makefile firefox-36-libzg/extension/Makefile @@ -329,23 +331,30 @@ if test "x${PLUGINS}" != "x" ; then AC_CONFIG_FILES([geany/Makefile]) continue 2 ;; - gedit) + gedit) AC_CONFIG_FILES([gedit/Makefile]) continue 2 - ;; + ;; tomboy) AC_CONFIG_FILES([tomboy/Makefile]) continue 2 ;; - rhythmbox) + rhythmbox) AC_CONFIG_FILES([rhythmbox/Makefile]) continue 2 - ;; - totem-libzg) + ;; + thunderbird) + AC_CONFIG_FILES([ + thunderbird/Makefile + thunderbird/extension/Makefile + ]) + continue 2 + ;; + totem-libzg) AC_CONFIG_FILES([totem-libzg/Makefile]) continue 2 ;; - monodevelop) + monodevelop) AC_CONFIG_FILES([monodevelop/Makefile]) continue 2 ;; @@ -357,11 +366,11 @@ if test "x${PLUGINS}" != "x" ; then AC_CONFIG_FILES([emacs/Makefile]) continue 2 ;; - telepathy) - AC_CONFIG_FILES([telepathy/Makefile]) - continue 2 - ;; - xchat) + telepathy) + AC_CONFIG_FILES([telepathy/Makefile]) + continue 2 + ;; + xchat) AC_CONFIG_FILES([xchat/Makefile]) continue 2 ;; diff --git a/emacs/zeitgeist.el b/emacs/zeitgeist.el index d8ad73c..699919b 100755 --- a/emacs/zeitgeist.el +++ b/emacs/zeitgeist.el @@ -1,6 +1,7 @@ ;;; The Zeitgeist Emacs Script -- integrates Emacs with Zeitgeist. ;;; Copyright (C) 2010, Patrick M. Niedzielski <PatrickNiedzielski@gmail.com> ;;; Copyright (C) 2011, Tassilo Horn <tassilo@member.fsf.org> +;;; Copyright (C) 2012, Jo Lund Steffensen <jonlst@gmail.com> ;;; ;;; This program is free software: you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by @@ -20,6 +21,7 @@ ;;* Code +(require 'cl) (require 'dbus) ;;** General Functions diff --git a/eog/zeitgeist.eog-plugin b/eog/zeitgeist.eog-plugin index 1cbb231..0ab0358 100644 --- a/eog/zeitgeist.eog-plugin +++ b/eog/zeitgeist.eog-plugin @@ -1,4 +1,4 @@ -[Eog Plugin] +[Plugin] Module=zeitgeist_plugin IAge=2 Loader=python diff --git a/firefox/Makefile.am b/firefox/Makefile.am index 8d8c576..08af9a4 100644 --- a/firefox/Makefile.am +++ b/firefox/Makefile.am @@ -5,9 +5,9 @@ TARGET_PROFILE = *default* local-install: all $(MAKE) -C extension $@ - cp xpcom-firefox\@zeitgeist-project.com.xpi ~/.mozilla/firefox/$(TARGET_PROFILE)/extensions/ + cp xpcom-firefox@zeitgeist-project.com.xpi ~/.mozilla/firefox/$(TARGET_PROFILE)/extensions/ local-uninstall: $(MAKE) -C extension $@ - -rm -rf ~/.mozilla/firefox/$(TARGET_PROFILE)/extensions/xpcom-firefox\@zeitgeist-project.com.xpi + -rm -rf ~/.mozilla/firefox/$(TARGET_PROFILE)/extensions/xpcom-firefox@zeitgeist-project.com.xpi diff --git a/firefox/README b/firefox/README index 3bad3df..9d8fecc 100644 --- a/firefox/README +++ b/firefox/README @@ -6,5 +6,5 @@ Useful resources: TODO ==== - * add events for downloads, see https://developer.mozilla.org/en/nsIDownloadProgressListener * add ability to delete events (Zeitgeist should mirror Firefox's history) + * add CloseEvents diff --git a/firefox/extension/chrome/content/event.js b/firefox/extension/chrome/content/event.js index 9eb1a2a..bd59b25 100644 --- a/firefox/extension/chrome/content/event.js +++ b/firefox/extension/chrome/content/event.js @@ -1,31 +1,58 @@ +function getOriginFromUri(uri) { + var parts = uri.split("://", 2); + if (parts[0] == "file") { + // Local file URI, we just trim it to directory in which the file is + return uri.substring(0, uri.lastIndexOf('/') + 1); + } else { + // HTTP(S), FTP or any other URI, we only take up to the domain name + return parts[0] + "://" + parts[1].split("/")[0] + "/"; + } +} + +function getManifestationFromUri(uri) { + var parts = uri.split("://", 2); + if (parts[0] == "file") { + return "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#FileDataObject"; + } else if (parts[0] == "http" || parts[0] == "https") { + return "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#WebDataObject"; + } else { + // May be FTP or something else + return "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#RemoteDataObject"; + } +} + +/* +function isDebugEnabled() { + var prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + return prefs.getBoolPref("extensions.zeitgeist.log"); +} +*/ + +function isPrivateBrowsing() { + var pbs = Components.classes["@mozilla.org/privatebrowsing;1"] + .getService(Components.interfaces.nsIPrivateBrowsingService); + return pbs.privateBrowsingEnabled; +} + var ZeitgeistProgressListener = { onStateChange: function(aBrowser, aProgress, aRequest, aStateFlags) { - - //Don't do anything if Private Browsing is enabled - var pbs = Components.classes["@mozilla.org/privatebrowsing;1"] - .getService(Components.interfaces.nsIPrivateBrowsingService); - if (pbs.privateBrowsingEnabled) return; - + // Don't do anything if Private Browsing is enabled + if (isPrivateBrowsing()) return; + if (aStateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP) { let uri = aBrowser.currentURI.spec; - //Ignore pages without titles (generally redirect pages) + // Ignore pages without titles (generally redirect pages) if (aBrowser.contentTitle == "") return; let mimetype = aBrowser.contentDocument.contentType; if (aRequest.name == uri && !this.ignore_uri(uri)) { - var origin; - var parts = uri.split("://", 2); - if (parts[0] == "file") { - // Local file URI, we just trim it to directory in which the file is - origin = uri.substring(0, uri.lastIndexOf('/') + 1); - } else { - // HTTP(S), FTP or any other URI, we only take up to the domain name - origin = parts[0] + "://" + parts[1].split("/")[0] + "/"; - } - - let subject = libzeitgeist.zeitgeist_subject_new_full( uri, + let origin = getOriginFromUri(uri); + let subject_manifestation = getManifestationFromUri(uri); + + let subject = libzeitgeist.zeitgeist_subject_new_full(uri, "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Website", - "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#RemoteDataObject", + subject_manifestation, mimetype, origin, aBrowser.contentTitle, @@ -38,11 +65,9 @@ var ZeitgeistProgressListener = { null); libzeitgeist.zeitgeist_log_insert_events_no_reply(libzeitgeist.log, event, null); - - //Log event in Firefox's error console if logging pref is true - var prefs = Components.classes["@mozilla.org/preferences-service;1"] - .getService(Components.interfaces.nsIPrefBranch); - if (prefs.getBoolPref("extensions.zeitgeist.log")) { + + /* + if (isDebugEnabled()) { zeitgeist.debug("Event added to zeitgeist:" + "\n\t\tevent interpretation: EVENT_INTERPRETATION.ACCESS_EVENT" + "\n\t\tEvent manifestation: EVENT_MANIFESTATION.USER_ACTIVITY" + @@ -55,6 +80,7 @@ var ZeitgeistProgressListener = { "\n\t\t\ttitle: " + aBrowser.contentTitle + "\n\t\t\tstorage: net"); } + */ var googlemail_view_regex = new RegExp("mail\\.google\\.com"); if (ZeitgeistPrefObserver.get_bool("enable_googlemail") & googlemail_view_regex.test(uri)) { @@ -89,7 +115,7 @@ var ZeitgeistProgressListener = { onSecurityChange: function(){}, onProgressChange: function(){}, - //Useful helpers + // Useful helpers ignore_uri: function(uri) { for (pattern in ZeitgeistPrefObserver.ignored_uris) { if (ZeitgeistPrefObserver.ignored_uris[pattern].test(uri)) { @@ -100,10 +126,56 @@ var ZeitgeistProgressListener = { }, }; +var ZeitgeistDownloadManagerListener = { + onDownloadStateChange: function(state, dl) { + // FIXME: DOWNLOAD_FINISHED would probably be more awesome, but + // it doesn't seem to be sent correctly :( + if (state != 0 /*DOWNLOAD_DOWNLOADING*/) return; + if (isPrivateBrowsing()) return; + + let uri = 'file://' + dl.targetFile.path; + let subject = libzeitgeist.zeitgeist_subject_new_full( + uri, + "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#Website", + "", // empty manifestation, Bluebird Alpha 2+ will figure it out + "", // FIXME: not working: dl.MIMEInfo.type + getOriginFromUri(uri), + dl.displayName, + "" // figure our storage (in case of saving to usb/sftp/etc) + ); + + // FIXME: libzeitgeist is missing event origin! + // FIXME: where do we put the original download URI? event origin + // is already supposed to be the page where you clicked + // the download link... + let event_ = libzeitgeist.zeitgeist_event_new_full( + "http://www.zeitgeist-project.com/ontologies/2010/01/27/zg#CreateEvent", + "http://www.zeitgeist-project.com/ontologies/2010/01/27/zg#UserActivity", + "application://firefox.desktop", + subject, + null + ); + + libzeitgeist.zeitgeist_log_insert_events_no_reply(libzeitgeist.log, event_, null); + }, + + onSecurityChange: function() {}, + onProgressChange: function() {}, + onStateChange: function() {} +}; + var zeitgeist = { init: function() { ZeitgeistPrefObserver.register(); + + // Listen to tab changes gBrowser.addTabsProgressListener(ZeitgeistProgressListener); + + // Listen to downloads + var downloadManager = Components.classes["@mozilla.org/download-manager;1"] + .getService(Components.interfaces.nsIDownloadManager); + downloadManager.addListener(ZeitgeistDownloadManagerListener); + libzeitgeist.init(); }, diff --git a/firefox/extension/chrome/content/module.js b/firefox/extension/chrome/content/module.js index 8f54c99..437486a 100644 --- a/firefox/extension/chrome/content/module.js +++ b/firefox/extension/chrome/content/module.js @@ -1,6 +1,6 @@ var libzeitgeist = { - zeitgeistPath: "libzeitgeist-1.0.so", + zeitgeistPath: "libzeitgeist-1.0.so.1", lib: null, diff --git a/firefox/extension/install.rdf b/firefox/extension/install.rdf index 6503b2d..44b0288 100644 --- a/firefox/extension/install.rdf +++ b/firefox/extension/install.rdf @@ -19,8 +19,8 @@ </em:targetApplication> <!-- Front End MetaData --> - <em:name>Zeitgeist</em:name> - <em:description>Dataprovider for the zeitgeist framework</em:description> + <em:name>Zeitgeist data-source</em:name> + <em:description>Data-source to send events to Zeitgeist</em:description> <em:creator>Markus Korn</em:creator> <em:contributor>Mark Tully</em:contributor> <em:homepageURL>http://launchpad.net/zeitgeist</em:homepageURL> diff --git a/npapi-plugin/np-zeitgeist.cc b/npapi-plugin/np-zeitgeist.cc index 6c2c04a..0066ab0 100644 --- a/npapi-plugin/np-zeitgeist.cc +++ b/npapi-plugin/np-zeitgeist.cc @@ -51,6 +51,7 @@ hasMethod(NPObject* obj, NPIdentifier methodName) { if (!strcmp(name, "insertEvent")) return true; else if (!strcmp(name, "setActor")) return true; + else if (!strcmp(name, "saveSnapshot")) return true; return false; } @@ -59,6 +60,7 @@ static bool invokeInsertEvent (NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result) { /* args should be: url, origin, mimetype, title, [interpretation] */ + g_debug("Inserting event"); char *url, *origin, *mimetype, *title; char *interpretation = NULL; const char *manifestation = NULL; @@ -66,9 +68,11 @@ invokeInsertEvent (NPObject *obj, const NPVariant *args, uint32_t argCount, NPVa const NPString *np_s; ZeitgeistEvent *event; + g_debug("arg count: %d", argCount); if(argCount < 4 || argCount > 6) { npnfuncs->setexception(obj, "exception during invocation"); + g_debug("too many or too few args"); return false; } @@ -145,6 +149,64 @@ invokeInsertEvent (NPObject *obj, const NPVariant *args, uint32_t argCount, NPVa } static bool +invokeSaveSnapshot (NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result) +{ + char *url; + char *screenshotURL = NULL; + const NPString *np_s; + np_s = &NPVARIANT_TO_STRING (args[0]); + url = g_strndup(np_s->UTF8Characters, np_s->UTF8Length); + np_s = &NPVARIANT_TO_STRING (args[1]); + screenshotURL = g_strndup(np_s->UTF8Characters, np_s->UTF8Length); + + if (!screenshotURL) + return false; + + gsize len = strlen(screenshotURL) - 22; + char *img = new char[len]; + memset(img, 0, len); + memcpy(img, screenshotURL + 22, len); + + // update thumbnail + gchar *thumbnail_path; + gchar *thumbnail_filename = NULL; + gchar *thumbnail_dir; + gchar *csum; + + // create dir if it doesn't exist + thumbnail_dir = g_build_filename (g_get_home_dir (), + ".thumbnails", + "large", + NULL); + if (!g_file_test(thumbnail_dir, G_FILE_TEST_IS_DIR)) { + g_mkdir_with_parents (thumbnail_dir, 0755); + } + g_free (thumbnail_dir); + + csum = g_compute_checksum_for_string (G_CHECKSUM_MD5, url, -1); + + thumbnail_filename = g_strconcat (csum, ".png", NULL); + thumbnail_path = g_build_filename (g_get_home_dir (), + ".thumbnails", + "large", + thumbnail_filename, + NULL); + g_free (csum); + + guchar *jpg_data = g_base64_decode(img, &len); + + g_debug("Writing thumbnail to %s", thumbnail_path); + g_file_set_contents(thumbnail_path, (gchar*)jpg_data, len, NULL); + + g_free (img); + g_free (jpg_data); + g_free (thumbnail_filename); + g_free (thumbnail_path); + + return true; +} + +static bool invokeSetActor (NPObject *obj, const NPVariant *args, uint32_t argCount, NPVariant *result) { const NPString *np_s; @@ -175,6 +237,7 @@ invoke(NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_t a name = npnfuncs->utf8fromidentifier(methodName); + g_debug("Calling %s", name); if(name) { if (!strcmp (name, "insertEvent")) @@ -185,6 +248,10 @@ invoke(NPObject* obj, NPIdentifier methodName, const NPVariant *args, uint32_t a { return invokeSetActor(obj, args, argCount, result); } + else if (!strcmp (name, "saveSnapshot")) + { + return invokeSaveSnapshot(obj, args, argCount, result); + } } npnfuncs->setexception(obj, "exception during invocation"); diff --git a/telepathy/zeitgeist-telepathy-observer b/telepathy/zeitgeist-telepathy-observer index 403f930..7bf8b7c 100755 --- a/telepathy/zeitgeist-telepathy-observer +++ b/telepathy/zeitgeist-telepathy-observer @@ -1,547 +1,322 @@ #!/usr/bin/env python -# -# This observer tracks text conversation and reports events to the Zeitgeist -# engine. Eventually, it will report file transfers and media streams -# -# ZConnections and ZChannels are tracked by the ZObserver, their invalidation -# is signalled by GObject signals. ZObserver.ObserveChannels uses asynchronous -# return functions to only return from the D-Bus method once all of the channels -# are prepared, this gives the Observer time to investigate the pending message -# queue before any Handlers can acknowledge it. -# -# Authors: Danielle Madeley <danielle.madeley@collabora.co.uk> -# Morten Mjelva <morten.mjelva@gmail.com> -# -import dbus, dbus.glib -import gobject - -import sys - -from time import time - -import logging -logging.basicConfig(level=logging.DEBUG) - -import telepathy -from telepathy.constants import CONNECTION_STATUS_DISCONNECTED, \ - HANDLE_TYPE_CONTACT - -from telepathy.interfaces import CHANNEL, \ - CHANNEL_TYPE_FILE_TRANSFER, \ - CHANNEL_TYPE_STREAMED_MEDIA, \ - CHANNEL_TYPE_TEXT, \ - CLIENT, \ - CLIENT_OBSERVER, \ - CONNECTION, \ - CONNECTION_INTERFACE_ALIASING, \ - CONNECTION_INTERFACE_CONTACTS +from gi.repository import TelepathyGLib as Tp +from gi.repository import GObject, Gio from zeitgeist.client import ZeitgeistClient from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation - -def error(*args): - logging.error("error %s" % args) - -# Create a connection to the Zeitgeist engine -try: - zclient = ZeitgeistClient() -except RuntimeError, e: - logging.error("Unable to connect to Zeitgeist. Won't send events. Reason: \ - %s" % e) - zclient = None - -class ZConnection(gobject.GObject, telepathy.client.Connection): - """ - Extends telepathy.client.Connection, storing information about proxy obj - - Object extends tp.Connection object, letting us store information about the - connection proxy locally. - Arguments: - path: Unique path to this connection - ready_handler: Callback function run once we're done setting up object - """ - __gsignals__ = { - 'disconnected': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - } - - def __init__(self, path, ready_handler): - service_name = path.replace('/', '.')[1:] - - # Ready callback - self.ready_handler = ready_handler - self.signals = [] - - gobject.GObject.__init__(self) - telepathy.client.Connection.__init__(self, service_name, path, - ready_handler = self._connection_ready) - - def __repr__(self): - return "ZConnection(%s)" % self.object_path - - def _status_changed(self, status, reason): - if status == CONNECTION_STATUS_DISCONNECTED: - for signal in self.signals: - signal.remove() - - self.emit('disconnected') - - def _connection_ready(self, conn): - def interfaces(interfaces): - self.contact_attr_interfaces = interfaces - - # Connection ready - self.ready_handler(conn) - - # Get contact attribute interfaces - self[dbus.PROPERTIES_IFACE].Get(CONNECTION_INTERFACE_CONTACTS, - 'ContactAttributeInterfaces', - reply_handler=interfaces, - error_handler=error) - - self.signals.append(self[CONNECTION].connect_to_signal( - 'StatusChanged', self._status_changed)) - - # This is necessary so our signal isn't sent over D-Bus - def do_disconnected(self): - pass -gobject.type_register(ZConnection) - -class ZChannel(gobject.GObject, telepathy.client.Channel): - """ - Extends telepathy.client.Channel - - This class allows us to store information about a telepathy.client.Channel - object locally. - Arguments: - account_path: Path to the account used. Passed to ObserveChannels. - connection: The connection used for the channel - properties: Properties of the channel we're proxying, from ObserveChannels - ready_handler: Callback function run once we're done setting up object - """ - __gsignals__ = { - 'closed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - } - - def __init__(self, account_path, connection, path, properties, ready_handler): - self.account_path = account_path - self.conn = connection - self.properties = properties - self.ready_handler = ready_handler - - self.signals = [] - - gobject.GObject.__init__(self) - telepathy.client.Channel.__init__(self, connection.service_name, path, - ready_handler=self._channel_ready) - - def __repr__(self): - return "ZChannel(%s)" % self.object_path - - def _send_to_zeitgeist(self, subjects, event_details): - event_details['subjects'] = subjects - zevent = Event.new_for_values(**event_details) - zclient.insert_event(zevent) - - def _channel_closed_cb(self): - for signal in self.signals: - signal.remove() - - self.emit('closed') - - def _default_operations_finished(self): - self._release_channel() - - def _get_contact_attributes_cb(self, attributes_map): - handle = self.properties[CHANNEL + '.TargetHandle'] +from zeitgeist.mimetypes import get_interpretation_for_mimetype + +import json + +GObject.threads_init() + +ZG_ACTOR = "dbus://org.freedesktop.Telepathy.Logger.service" +TP_ACCOUNT_PATH = "x-telepathy-account-path:%s" +TP_IDENTIFIER = "x-telepathy-identifier:%s" + +dbus = Tp.DBusDaemon.dup() +zg_client = ZeitgeistClient() + +def callback (ids): + print ids + +def error_handler (error): + print error + +def create_event(account, channel): + target = channel.get_target_contact() + event_template = Event.new_for_values( + actor = ZG_ACTOR, + interpretation = Interpretation.ACCESS_EVENT, + manifestation = Manifestation.USER_ACTIVITY \ + if channel.get_properties("requested") else Manifestation.WORLD_ACTIVITY, + origin = TP_ACCOUNT_PATH % account.get_object_path()[len(Tp.ACCOUNT_OBJECT_PATH_BASE):]) + event_template.subjects.append( + Subject.new_for_values( + uri = "", + interpretation = Interpretation.IMMESSAGE, + manifestation = Manifestation.SOFTWARE_SERVICE, + mimetype = "plain/text", + origin = TP_IDENTIFIER % target.get_identifier(), + text = target.get_alias(), + storage = "net")) + event_template.subjects.append( + Subject.new_for_values( + uri = TP_IDENTIFIER % target.get_identifier(), + interpretation = Interpretation.CONTACT, + manifestation = Manifestation.CONTACT_LIST_DATA_OBJECT, + origin = TP_IDENTIFIER % target.get_identifier(), + text = target.get_alias(), + storage = "net")) + return event_template + +def print_channel(event): + print "Event:" + print " - timestamp:", event.timestamp + print " - actor", event.actor + print " - interpretation:", event.interpretation + print " - manifestation:", event.manifestation + print " - origin:", event.origin + print " - subjects:", len(event.subjects) + for i, subject in enumerate(event.subjects): + print " - subject %i:" % (i+1) + print " - uri:", subject.uri + print " - interpretation:", subject.interpretation + print " - manifestation:", subject.manifestation + print " - mimetype:", subject.mimetype + print " - origin:", subject.origin + print " - text:", subject.text + print " - storage:", subject.storage + print " -payload:", event.payload + zg_client.insert_events([event], callback, error_handler) + +""" +Handling of text based events +""" + +def msg_recv_callback (channel, message, account): + if message.is_delivery_report(): + return + print "=== RECEIVED MSG ===" + event_template = create_event (account, channel) + event_template.interpretation = Interpretation.RECEIVE_EVENT + event_template.manifestation = Manifestation.WORLD_ACTIVITY + print_channel(event_template) + +def msg_sent_callback (channel, message, flags, token, account): + print "=== SENT MSG ===" + event_template = create_event (account, channel) + event_template.interpretation = Interpretation.SEND_EVENT + event_template.manifestation = Manifestation.USER_ACTIVITY + print_channel(event_template) + +def channel_closed_callback (channel, domain, code, message, account): + print "=== CLOSED CHANNEL ===" + event_template = create_event (account, channel) + event_template.interpretation = Interpretation.LEAVE_EVENT + print_channel(event_template) + +def observe_textchannels(observer, account, connection, channels, + dispatch_op, requests, context, user_data): + try: + for channel in channels: + target = channel.get_target_contact() + if not target: + continue + event_template = create_event(account, channel) + print "=== CREATED CHANNEL ===" + print_channel(event_template) + for message in channel.get_pending_messages(): + msg_recv_callback(channel, message, account) + event_template = create_event(account, channel) + channel.connect('invalidated', channel_closed_callback, account) + channel.connect('message-received', msg_recv_callback, account) + channel.connect('message-sent', msg_sent_callback, account) + finally: + context.accept() + +factory = Tp.AutomaticClientFactory.new (dbus) + +factory.add_channel_features ([Tp.Channel.get_feature_quark_contacts()]) +factory.add_contact_features ([Tp.ContactFeature.ALIAS]) +text_observer = Tp.SimpleObserver.new_with_factory(factory, True, + 'ZeitgeistTextObserver', True, observe_textchannels, None) +text_observer.add_observer_filter({ + Tp.PROP_CHANNEL_CHANNEL_TYPE: Tp.IFACE_CHANNEL_TYPE_TEXT, + Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE: int(Tp.HandleType.CONTACT), + +}) +text_observer.register() + + +""" +Handling of call based events +""" + +call_timers = {} + +def create_call_event(account, channel): + targets = channel.get_members() + if not targets: + return + event_template = Event.new_for_values( + actor = ZG_ACTOR, + interpretation = Interpretation.ACCESS_EVENT, + manifestation = Manifestation.USER_ACTIVITY \ + if channel.get_property("requested") else Manifestation.WORLD_ACTIVITY, + origin = TP_ACCOUNT_PATH % account.get_object_path()[len(Tp.ACCOUNT_OBJECT_PATH_BASE):]) + for i, target in enumerate(targets): + if i == 0: + event_template.subjects.append ( + Subject.new_for_values( + uri = "", + interpretation = Interpretation.MEDIA.AUDIO, + manifestation = Manifestation.MEDIA_STREAM, + mimetype = "x-telepathy/call", + origin = TP_IDENTIFIER % target.get_identifier(), + text = target.get_alias(), + storage = "net")) + event_template.subjects.append ( + Subject.new_for_values( + uri = TP_IDENTIFIER % target.get_identifier(), + interpretation = Interpretation.CONTACT, + manifestation = Manifestation.CONTACT_LIST_DATA_OBJECT, + origin = TP_IDENTIFIER % target.get_identifier(), + text = target.get_alias(), + storage = "net")) + return event_template + + +def call_state_changed (channel, state, flags, reason, details, account): + #FIXME: Something breaks this + made_by_user = False + if reason.actor == channel.get_property("connection").get_self_handle(): + made_by_user = True + #print "User Data:", user_data + #print "Event Template: ", event_template + if state in (3, 5, 6): + event_template = create_call_event (account, channel) + event_template.manifestation = Manifestation.USER_ACTIVITY if made_by_user \ + else Manifestation.WORLD_ACTIVITY + + if state == Tp.CallState.INITIALISED: + event_template.interpretation = Interpretation.CREATE_EVENT + call_timers[channel] = 0 + print_channel(event_template) + + elif state == Tp.CallState.ACTIVE: + event_template.interpretation = Interpretation.ACCESS_EVENT + call_timers[channel] = int(event_template.timestamp) + print_channel(event_template) + + elif state == Tp.CallState.ENDED: + if call_timers.has_key(channel): + event_template.interpretation = Interpretation.LEAVE_EVENT + if reason.reason.numerator == Tp.CallStateChangeReason.REJECTED: + event_template.interpretation = Interpretation.DENY_EVENT + elif reason.reason.numerator == Tp.CallStateChangeReason.NO_ANSWER: + event_template.interpretation = Interpretation.EXPIRE_EVENT + + duration = 0 if not call_timers.has_key(channel) \ + else int(event_template.timestamp) - call_timers[channel] + details = {"http://zeitgeist-project.com/1.0/telepathy/call": { + "state": channel.get_state()[0].numerator, + "reason": channel.get_state()[1].numerator, + "requested": channel.get_property("requested"), + "host": TP_ACCOUNT_PATH % account.get_object_path()[len(Tp.ACCOUNT_OBJECT_PATH_BASE):] \ + if channel.get_property("requested") else event_template.subjects[1].uri, + "receiver": TP_IDENTIFIER % event_template.subjects[1].uri \ + if channel.get_property("requested") \ + else TP_ACCOUNT_PATH % account.get_object_path()[len(Tp.ACCOUNT_OBJECT_PATH_BASE):], + "duration": duration + }} + details = json.dumps(details) + event_template.payload = details.encode("utf-8") + del call_timers[channel] + print_channel(event_template) + +def observe_callchannels(observer, account, connection, channels, + dispatch_op, requests, context, user_data): try: - attributes = attributes_map[handle] - self._target_alias = attributes[CONNECTION_INTERFACE_ALIASING + - '/alias'] - except KeyError: - self._target_alias = self._target_id - - self._connect_to_signals() - self._default_operations_finished() - - def _get_requested_cb(self, req): - handle = self.properties[CHANNEL + '.TargetHandle'] - self._channel_requested = req - - # Get contact name - self.conn[CONNECTION_INTERFACE_CONTACTS].GetContactAttributes( - [handle], - # assuming this is available is technically wrong - [ CONNECTION_INTERFACE_ALIASING ], - False, - reply_handler=self._get_contact_attributes_cb, - error_handler=error) - - def _release_channel(self): - # Finished with critical tasks. - self.ready_handler(self) - - def _connect_to_signals(self): - # Connect to signals - self.signals.append(self[CHANNEL].connect_to_signal('Closed', - self._channel_closed_cb)) - - # Called when channel proxy becomes ready. Chains method calls so actions - # which depend on each other are executed in order. - def _channel_ready(self, channel): - self._target_id = self.properties[CHANNEL + '.TargetID'] - - # Get contact attribute interfaces - self[dbus.PROPERTIES_IFACE].Get(CHANNEL, - 'Requested', - reply_handler=self._get_requested_cb, - error_handler=error) - - # This is necessary so our signal isn't sent over D-Bus - def do_closed(self): - pass -gobject.type_register(ZChannel) - -class ZTextChannel(ZChannel): - def __init__(self, account_path, connection, path, properties, ready_handler): - ZChannel.__init__(self, account_path, connection, path, properties, ready_handler) - - self._subject = None - - def __repr__(self): - return "ZTextChannel(%s)" % self.object_path - - def _release_channel(self): - # Finished with critical tasks. - self.ready_handler(self) - - # Report to zeitgeist - if not self._subject: - self._subject = [Subject.new_for_values( - uri = "telepathy://" + self.account_path + "/" + self._target_id, - interpretation = unicode(Interpretation.IMMESSAGE), - manifestation = unicode(Manifestation.SOFTWARE_SERVICE), - origin = self.account_path, - mimetype = "text/plain", - text = self._target_alias)] - - if self._channel_requested: - manifestation = unicode(Manifestation.USER_ACTIVITY) - else: - manifestation = unicode(Manifestation.WORLD_ACTIVITY) - - timestamp = int(time() * 1000) - - event = Event.new_for_values( - timestamp = timestamp, - interpretation = unicode(Interpretation.ACCESS_EVENT), - manifestation = manifestation, - actor = self.account_path, - subjects = self._subject) - zclient.insert_event(event) - - def _channel_closed_cb(self): - for signal in self.signals: - signal.remove() - - timestamp = int(time() * 1000) - - event = Event.new_for_values( - timestamp = timestamp, - interpretation = unicode(Interpretation.LEAVE_EVENT), - manifestation = unicode(Manifestation.USER_ACTIVITY), - actor = self.account_path, - subjects = self._subject) - zclient.insert_event(event) - - self.emit('closed') - - def _get_pending_messages_cb(self, messages): - for message in messages: - self._message_received_cb(*message) - - self._release_channel() - - def _default_operations_finished(self): - self[CHANNEL_TYPE_TEXT].ListPendingMessages( - False, - reply_handler=self._get_pending_messages_cb, - error_handler=error) - - def _message_received_cb(self, identification, timestamp, sender, contenttype, - flags, content): - logging.debug("Message received") - if not self._subject: - self._subject = [Subject.new_for_values( - uri = "telepathy://" + self.account_path + "/" + self._target_id, - interpretation = unicode(Interpretation.IMMESSAGE), - manifestation = unicode(Manifestation.SOFTWARE_SERVICE), - origin = self.account_path, - mimetype = "text/plain", - text = self._target_alias)] - - timestamp = timestamp * 1000 - - event = Event.new_for_values( - timestamp = timestamp, - interpretation = unicode(Interpretation.RECEIVE_EVENT), - manifestation = unicode(Manifestation.WORLD_ACTIVITY), - actor = self.account_path, - subjects = self._subject) - zclient.insert_event(event) - - def _message_sent_cb(self, timestamp, contenttype, content): - logging.debug("Message sent") - - timestamp = timestamp * 1000 - - event = Event.new_for_values( - timestamp = timestamp, - interpretation = unicode(Interpretation.SEND_EVENT), - manifestation = unicode(Manifestation.USER_ACTIVITY), - actor = self.account_path, - subjects = self._subject) - zclient.insert_event(event) - - def _connect_to_signals(self): - # Connect to signals - self.signals.append(self[CHANNEL_TYPE_TEXT].connect_to_signal('Sent', - self._message_sent_cb)) - self.signals.append(self[CHANNEL_TYPE_TEXT].connect_to_signal('Received', - self._message_received_cb)) - self.signals.append(self[CHANNEL].connect_to_signal('Closed', - self._channel_closed_cb)) - -class ZStreamedMediaChannel(ZChannel): - def __init__(self, account_path, connection, path, properties, ready_handler): - self._audio_subject = None - self._video_subject = None - - self.stream_cache = {} - - ZChannel.__init__(self, account_path, connection, path, properties, ready_handler) - - def __repr__(self): - return "ZStreamedMediaChannel(%s)" % self.object_path - - def _channel_closed_cb(self): - for signal in self.signals: - signal.remove() - - self.emit('closed') - - def _stream_added_cb(self, streamid, handle, streamtype): - logging.debug("Stream added. Streamtype: " + str(streamtype)) - self.stream_cache[streamid] = {'handle': handle, 'streamtype': streamtype} - - if not self._audio_subject: - self._audio_subject = [Subject.new_for_values( - uri = "telepathy://" + self.account_path + "/" + self._target_id, - interpretation = unicode(Interpretation.AUDIO), - manifestation = unicode(Manifestation.MEDIA_STREAM), - origin = self.account_path, - mimetype = "text/plain", - text = self._target_alias)] - - if not self._video_subject: - self._video_subject = [Subject.new_for_values( - uri = "telepathy://" + self.account_path + "/" + self._target_id, - interpretation = unicode(Interpretation.VIDEO), - manifestation = unicode(Manifestation.MEDIA_STREAM), - origin = self.account_path, - mimetype = "text/plain", - text = self._target_alias)] - - if streamtype == 0: - subject = self._audio_subject - elif streamtype == 1: - subject = self._video_subject - - if self._channel_requested: - manifestation = unicode(Manifestation.USER_ACTIVITY) - else: - manifestation = unicode(Manifestation.WORLD_ACTIVITY) - - timestamp = int(time() * 1000) - event = Event.new_for_values( - timestamp = timestamp, - interpretation = unicode(Interpretation.ACCESS_EVENT), - manifestation = manifestation, - actor = self.account_path, - subjects = subject) - zclient.insert_event(event) - - - def _stream_removed_cb(self, streamid): - streamtype = self.stream_cache[streamid]['streamtype'] - logging.debug("Stream removed. Streamtype: " + str(streamtype)) - - if streamtype == 0: - subject = self._audio_subject - elif streamtype == 1: - subject = self._video_subject - - timestamp = int(time() * 1000) - event = Event.new_for_values( - timestamp = timestamp, - interpretation = unicode(Interpretation.LEAVE_EVENT), - manifestation = unicode(Manifestation.USER_ACTIVITY), - actor = self.account_path, - subject = subject) - zclient.insert_event(event) - - del self.stream_cache[streamid] - - def _list_streams_cb(self, streams): - for stream in streams: - self._stream_added_cb(stream[0], stream[1], stream[2]) - - def _default_operations_finished(self): - self._release_channel() - - self[CHANNEL_TYPE_STREAMED_MEDIA].ListStreams( - reply_handler=self._list_streams_cb, - error_handler=error) - - def _connect_to_signals(self): - # Connect to signals - self.signals.append(self[CHANNEL_TYPE_STREAMED_MEDIA].connect_to_signal('StreamAdded', - self._stream_added_cb)) - self.signals.append(self[CHANNEL_TYPE_STREAMED_MEDIA].connect_to_signal('StreamRemoved', - self._stream_removed_cb)) - self.signals.append(self[CHANNEL].connect_to_signal('Closed', - self._channel_closed_cb)) - -class ZObserver(telepathy.server.Observer, - telepathy.server.DBusProperties): - """ - Extends telepathy.server.Observer, listening for events - - - ZObserver listens for a channel matching the filter is opened by telepathy, - and reacts by opening connection and channel proxies in order to liisten in - on events in the channel. - """ - - def __init__(self, client_name, enable_stream_observer=False, - enable_text_observer=False): - - service_name = '.'.join ([CLIENT, client_name]) - object_path = '/' + service_name.replace('.', '/') - - bus_name = dbus.service.BusName(service_name, bus=dbus.SessionBus()) - - telepathy.server.Observer.__init__(self, bus_name, object_path) - telepathy.server.DBusProperties.__init__(self) - - self._implement_property_get(CLIENT, { - 'Interfaces': lambda: [ CLIENT_OBSERVER ], - }) - - - filter_array = dbus.Array([], signature='a{sv}') - - if enable_stream_observer: - filter_array.append(dbus.Dictionary({ - CHANNEL + '.ChannelType': CHANNEL_TYPE_STREAMED_MEDIA, - CHANNEL + '.TargetHandleType': HANDLE_TYPE_CONTACT - }, signature='sv')) - - if enable_text_observer: - filter_array.append(dbus.Dictionary({ - CHANNEL + '.ChannelType': CHANNEL_TYPE_TEXT, - CHANNEL + '.TargetHandleType': HANDLE_TYPE_CONTACT - }, signature='sv')) - - self._implement_property_get(CLIENT_OBSERVER, { - 'ObserverChannelFilter': lambda: filter_array}) - - self.connection_cache = {} - self.channel_cache = {} - - # Set async callbacks so ObserveChannels won't return until we're finished - @dbus.service.method(CLIENT_OBSERVER, - in_signature='ooa(oa{sv})oaoa{sv}', out_signature='', - async_callbacks=('_success', '_error')) - def ObserveChannels(self, account, conn, channels, dispatch_operation, - requests_satisfied, observer_info, _success, _error): - - # List of channels we're waiting to finish before we release the bus - # with _success() - pending_channels = [] - - # Create ZChannel objects for the current connection - def create_channels(conn): - for path, properties in channels: - if path not in self.channel_cache: - chantype = properties[CHANNEL + '.ChannelType'] - if chantype == CHANNEL_TYPE_STREAMED_MEDIA: - chan = ZStreamedMediaChannel(account, conn, path, \ - properties, channel_ready) - elif chantype == CHANNEL_TYPE_TEXT: - chan = ZTextChannel(account, conn, path, \ - properties, channel_ready) - - pending_channels.append(chan) - self.channel_cache[path] = chan - - - # Called when the channel proxy is invalidated - def channel_closed(chan): - try: - del self.channel_cache[chan.object_path] - logging.debug("Channel closed: %s" % chan) - except: - logging.error("Couldn't delete channel: %s" % chan) - - # No more channels means we aren't needed. - # Telepathy will restart us when we are - if not self.channel_cache: - logging.warning("No channels left in list. Exiting") - sys.exit() - - # Called when ZChannel object has finished its tasks - # Removes channel from pending queue, and releases the connection - # if all channeled have been processed - def channel_ready(chan): - logging.debug("Channel ready: %s" % chan) - - pending_channels.remove(chan) - chan.connect('closed', channel_closed) - - if not pending_channels: - _success() - - # Called when the connection proxy is invalidated - def connection_closed(conn): - logging.debug("Connection closed: %s" % conn) - del self.connection_cache[conn.object_path] - - # Called when ZConnection object has finished its tasks - # Connects to 'disconnected' signal to listen for connection - # invalidation. Triggers creation of ZChannel objects for the - # connection - def connection_ready(conn): - logging.debug("Connection ready: %s" % conn) - conn.connect('disconnected', connection_closed) - create_channels(conn) - - # If the connection exists in cache, call the handler manually to - # set up channels. Otherwise, create the connection and cache it. - if conn not in self.connection_cache: - try: - logging.debug("Trying to create connection...") - self.connection_cache[conn] = ZConnection(conn, connection_ready) - except: - logging.error("Connection creation failed") - else: - logging.debug("Connection exists: %s" % conn) - create_channels(self.connection_cache[conn]) - -def main(): - ZObserver("Zeitgeist", enable_stream_observer=True, - enable_text_observer=True) - -if __name__ == '__main__': - gobject.timeout_add(0, main) - loop = gobject.MainLoop() - loop.run() + for channel in channels: + if channel.get_state() == Tp.CallState.INITIALISED: + event_template.interpretation = Interpretation.CREATE_EVENT + call_timers[channel] = 0 + print_channel(event_template) + channel.connect("state-changed", call_state_changed, account) + finally: + context.accept() + +factory = Tp.AutomaticClientFactory.new (dbus) +factory.add_channel_features ([Tp.Channel.get_feature_quark_contacts()]) +factory.add_contact_features ([Tp.ContactFeature.ALIAS]) + +call_observer = Tp.SimpleObserver.new_with_factory(factory, True, + 'ZeitgeistCallObserver', True, observe_callchannels, None) +call_observer.add_observer_filter({ + Tp.PROP_CHANNEL_CHANNEL_TYPE: Tp.IFACE_CHANNEL_TYPE_CALL, + Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE: int(Tp.HandleType.CONTACT), + +}) +call_observer.register() + +""" +Handling of file transfer based events +""" + +def ft_state_changed (channel, state, account): + state = channel.get_state()[0] + target = channel.get_target_contact() + print state, ZG_ACTOR + if state in (4,5): + # get attributes of the file being sent or received + attr = (Gio.FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, + Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, + Gio.FILE_ATTRIBUTE_STANDARD_SIZE ) + info = channel.get_property("file").query_info (",".join(attr), + Gio.FileQueryInfoFlags.NONE, None) + # setup new event to be sent to zeitgeist + event = Event.new_for_values ( + interpretation = Interpretation.SEND_EVENT \ + if channel.get_property("requested") else Interpretation.RECEIVE_EVENT, + manifestation = Manifestation.USER_ACTIVITY \ + if channel.get_property("requested") else Manifestation.WORLD_ACTIVITY, + actor = ZG_ACTOR, + origin = TP_ACCOUNT_PATH % account.get_object_path()[35:]) + subject = Subject.new_for_values ( + uri = channel.get_property("file").get_uri(), + interpretation = get_interpretation_for_mimetype(info.get_content_type()), + manifestation = Manifestation.FILE_DATA_OBJECT \ + if channel.get_property("requested") else Manifestation.FILE_DATA_OBJECT.REMOTE_DATA_OBJECT, + text = info.get_display_name(), + mimetype = info.get_content_type(), + origin = "/".join(channel.get_property("file").get_uri().split("/")[:-1])+"/" \ + if channel.get_property("requested") else TP_IDENTIFIER % target.get_identifier()) + event.subjects.append(subject) + subject = Subject.new_for_values ( + uri = TP_IDENTIFIER % target.get_identifier(), + interpretation = Interpretation.CONTACT.PERSON_CONTACT, + manifestation = Manifestation.CONTACT_LIST_DATA_OBJECT, + origin = TP_IDENTIFIER % target.get_identifier(), + text = target.get_alias(), + storage = "net") + event.subjects.append(subject) + details = {"http://zeitgeist-project.com/1.0/telepathy/filetransfer": { + "state": channel.get_state()[0].numerator, + "reason": channel.get_state()[1].numerator, + "requested": channel.get_property("requested"), + "sender": TP_ACCOUNT_PATH % account.get_object_path()[35:] \ + if channel.get_property("requested") else TP_IDENTIFIER % target.get_identifier(), + "receiver": TP_IDENTIFIER % target.get_identifier() \ + if channel.get_property("requested") else TP_ACCOUNT_PATH % account.get_object_path()[35:], + "mimetype": info.get_content_type(), + "date": channel.get_date().to_unix(), + "description": channel.get_description(), + "size": channel.get_size(), + "service": channel.get_service_name(), + "uri": channel.get_property("file").get_uri() + }} + details = json.dumps(details) + event.payload = details.encode("utf-8") + print_channel(event) + +def observe_ftchannels(observer, account, connection, channels, + dispatch_op, requests, context, user_data): + context.accept() + print "FT" + for channel in channels: + channel.connect("notify::state", ft_state_changed, account) + +ft_observer = Tp.SimpleObserver.new(dbus, True, 'ZeitgeistFTObserver', True, + observe_ftchannels, None) +ft_observer.add_observer_filter({ + Tp.PROP_CHANNEL_CHANNEL_TYPE: Tp.IFACE_CHANNEL_TYPE_FILE_TRANSFER, +}) +ft_observer.register() + +""" +Start the mainloop +""" + +main_loop = GObject.MainLoop() +main_loop.run() diff --git a/thunderbird/Makefile.am b/thunderbird/Makefile.am new file mode 100644 index 0000000..00b37f2 --- /dev/null +++ b/thunderbird/Makefile.am @@ -0,0 +1,13 @@ +SUBDIRS = extension + +# identifier for the TB profile in which 'make install' installs this extension +TARGET_PROFILE = *default* + +local-install: all + $(MAKE) -C extension $@ + cp xpcom-thunderbird\@zeitgeist-project.com.xpi ~/.thunderbird/$(TARGET_PROFILE)/extensions/ + +local-uninstall: + $(MAKE) -C extension $@ + -rm -rf ~/.thunderbird/$(TARGET_PROFILE)/extensions/xpcom-thunderbird\@zeitgeist-project.com.xpi + diff --git a/thunderbird/README b/thunderbird/README new file mode 100644 index 0000000..572bbe7 --- /dev/null +++ b/thunderbird/README @@ -0,0 +1,10 @@ +Links +===== + +Useful resources: + https://developer.mozilla.org/en/js-ctypes/Using_js-ctypes + +TODO +==== + * add events for attachments + * add events for calendar if thunderbird has lightning intalled diff --git a/thunderbird/extension/Makefile.am b/thunderbird/extension/Makefile.am new file mode 100644 index 0000000..b82e2de --- /dev/null +++ b/thunderbird/extension/Makefile.am @@ -0,0 +1,40 @@ +SUBDIRS = + +extensiondir = $(datadir)/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384} +EXTENSIONS_SYMLINK = $(extensiondir) + +xul_extdir = $(datadir)/xul-ext-zeitgeist +dist_xul_ext_DATA = \ + license.txt \ + chrome.manifest \ + install.rdf \ + $(NULL) + +contentdir = $(xul_extdir)/chrome/content +dist_content_DATA = \ + chrome/content/zeitgeist.xul \ + chrome/content/zeitgeist.png \ + chrome/content/event.js \ + chrome/content/module.js \ + $(NULL) + +prefsdir = $(xul_extdir)/defaults/preferences +dist_prefs_DATA = \ + defaults/preferences/prefs.js \ + $(NULL) + +install-exec-hook: + $(MKDIR_P) $(DESTDIR)$(extensiondir) + test -h "$(DESTDIR)$(EXTENSIONS_SYMLINK)" || $(LN_S) -f "$(DESTDIR)$(xul_extdir)" "$(DESTDIR)$(EXTENSIONS_SYMLINK)" + +# we want to allow also local install +EXTENSION_CONTENT = \ + $(dist_xul_ext_DATA) \ + defaults \ + chrome \ + $(NULL) + +local-install: all + zip -r ../xpcom-thunderbird@zeitgeist-project.com.xpi $(EXTENSION_CONTENT) + +local-uninstall: diff --git a/thunderbird/extension/chrome.manifest b/thunderbird/extension/chrome.manifest new file mode 100644 index 0000000..4d42dea --- /dev/null +++ b/thunderbird/extension/chrome.manifest @@ -0,0 +1,5 @@ +content zeitgeist chrome/content/ + +overlay chrome://messenger/content/messenger.xul chrome://zeitgeist/content/zeitgeist.xul + +locale zeitgeist en-US chrome/locale/en-US/ diff --git a/thunderbird/extension/chrome/content/event.js b/thunderbird/extension/chrome/content/event.js new file mode 100644 index 0000000..ee5a46c --- /dev/null +++ b/thunderbird/extension/chrome/content/event.js @@ -0,0 +1,85 @@ +var ZeitgeistNewMailListener = { + + init: function() { + var notificationService = Components.classes["@mozilla.org/messenger/msgnotificationservice;1"] + .getService(Components.interfaces.nsIMsgFolderNotificationService); + notificationService.addListener(this, notificationService.msgAdded); + }, + + msgAdded: function(aMsgHdr) { + + let folder = aMsgHdr.folder; + + // Only add to zeitgeist if the folder the message is in is flagged as an inbox ("0x00001000") + if (folder.flags & "0x00001000") { + + let hdrParser = Components.classes["@mozilla.org/messenger/headerparser;1"] + .getService(Components.interfaces.nsIMsgHeaderParser); + + let uri = folder.getUriForMsg(aMsgHdr); + let author = hdrParser.extractHeaderAddressName(aMsgHdr.mime2DecodedAuthor); + let address = hdrParser.extractHeaderAddressMailboxes(aMsgHdr.author); + let message_subject = aMsgHdr.mime2DecodedSubject + let account = aMsgHdr.folder.server.prettyName + + let subject = libzeitgeist.zeitgeist_subject_new_full( uri, + "http://www.semanticdesktop.org/ontologies/2007/03/22/nmo#Email", + "http://www.semanticdesktop.org/ontologies/2007/03/22/nmo#MailboxDataObject", + "message/rfc822", + address, + author + " - " + message_subject, + "net"); + + let event = libzeitgeist.zeitgeist_event_new_full( "http://www.zeitgeist-project.com/ontologies/2010/01/27/zg#ReceiveEvent", + "http://www.zeitgeist-project.com/ontologies/2010/01/27/zg#SystemNotification", + "application://thunderbird.desktop", + subject, + null); + + libzeitgeist.zeitgeist_event_set_origin( event, + account) + + libzeitgeist.zeitgeist_log_insert_events_no_reply(libzeitgeist.log, event, null); + + //Log event in Thunderbird's error console if logging pref is true + var prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + if (prefs.getBoolPref("extensions.zeitgeist.log")) { + zeitgeist.debug("Event added to zeitgeist:" + + "\n\t\tEvent interpretation: EVENT_INTERPRETATION.RECEIVE_EVENT" + + "\n\t\tEvent manifestation: EVENT_MANIFESTATION.SYSTEM_NOTIFICATION" + + "\n\t\tActor: application://thunderbird.desktop" + + "\n\t\tOrigin: " + account + + "\n\t\tSubject:\n\t\t\tSubject interpretation: MESSAGE.EMAIL" + + "\n\t\t\tSubject manifestation: MAILBOX_DATA_OBJECT" + + "\n\t\t\turl: " + uri + + "\n\t\t\tmimetype: message/rfc822" + + "\n\t\t\torigin: " + address + + "\n\t\t\ttitle: " + author + " - " + message_subject + + "\n\t\t\tstorage: net"); + } + } + }, +}; + +var zeitgeist = { + init: function() { + ZeitgeistNewMailListener.init(); + libzeitgeist.init(); + }, + + uninit: function() { + + libzeitgeist.shutdown(); + }, + + debug: function (aMessage) { + var consoleService = Components.classes["@mozilla.org/consoleservice;1"] + .getService(Components.interfaces.nsIConsoleService); + consoleService.logStringMessage("Zeitgeist Extension (" + new Date() + " ):\n\t" + aMessage); + window.dump("Zeitgeist Extension: (" + new Date() + " ):\n\t" + aMessage + "\n"); + } +}; + +window.addEventListener("load", function() {zeitgeist.init()}, false); +window.addEventListener("unload", function() {zeitgeist.uninit()}, false); diff --git a/thunderbird/extension/chrome/content/module.js b/thunderbird/extension/chrome/content/module.js new file mode 100644 index 0000000..ffd5ad2 --- /dev/null +++ b/thunderbird/extension/chrome/content/module.js @@ -0,0 +1,62 @@ +var libzeitgeist = { + + zeitgeistPath: "libzeitgeist-1.0.so.1", + + lib: null, + + init: function() { + + Components.utils.import("resource://gre/modules/ctypes.jsm"); + + this.lib = ctypes.open(this.zeitgeistPath); + + //Structures + this._ZeitgeistEvent = new ctypes.StructType("_ZeitgeistEvent"); + this._ZeitgeistSubject = new ctypes.StructType("_ZeitgeistSubject"); + this._ZeitgeistLog = new ctypes.StructType("_ZeitgeistLog"); + + //Methods + this.zeitgeist_event_new_full = this.lib.declare( "zeitgeist_event_new_full", + ctypes.default_abi, + ctypes.char.ptr, + ctypes.char.ptr, //interpretation + ctypes.char.ptr, //manifestation + ctypes.char.ptr, //actor + ctypes.char.ptr, //subject + ctypes.voidptr_t); + + this.zeitgeist_event_set_origin = this.lib.declare( "zeitgeist_event_set_origin", + ctypes.default_abi, + ctypes.voidptr_t, + ctypes.char.ptr, //event + ctypes.char.ptr); //origin + + this.zeitgeist_subject_new_full = this.lib.declare( "zeitgeist_subject_new_full", + ctypes.default_abi, + ctypes.char.ptr, + ctypes.char.ptr, //uri + ctypes.char.ptr, //interpretation + ctypes.char.ptr, //manfestation + ctypes.char.ptr, //mimetype + ctypes.char.ptr, //origin + ctypes.char.ptr, //text + ctypes.char.ptr); //storage + + this.zeitgeist_log_new = this.lib.declare( "zeitgeist_log_new", + ctypes.default_abi, + ctypes.char.ptr ); + + this.zeitgeist_log_insert_events_no_reply = this.lib.declare( "zeitgeist_log_insert_events_no_reply", + ctypes.default_abi, + ctypes.void_t.ptr, + ctypes.char.ptr, //log + ctypes.char.ptr, //event + ctypes.voidptr_t); + + this.log = libzeitgeist.zeitgeist_log_new(); + }, + + shutdown: function() { + this.lib.close(); + } +}; diff --git a/thunderbird/extension/chrome/content/zeitgeist.png b/thunderbird/extension/chrome/content/zeitgeist.png Binary files differnew file mode 100644 index 0000000..4ae18f0 --- /dev/null +++ b/thunderbird/extension/chrome/content/zeitgeist.png diff --git a/thunderbird/extension/chrome/content/zeitgeist.xul b/thunderbird/extension/chrome/content/zeitgeist.xul new file mode 100644 index 0000000..852c394 --- /dev/null +++ b/thunderbird/extension/chrome/content/zeitgeist.xul @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<overlay id="zeitgeist" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://zeitgeist/content/module.js"></script> + <script type="application/javascript" src="chrome://zeitgeist/content/event.js"></script> + +</overlay> diff --git a/thunderbird/extension/defaults/preferences/prefs.js b/thunderbird/extension/defaults/preferences/prefs.js new file mode 100644 index 0000000..c314a60 --- /dev/null +++ b/thunderbird/extension/defaults/preferences/prefs.js @@ -0,0 +1,2 @@ +pref("extensions.zeitgeist.log" , false); + diff --git a/thunderbird/extension/install.rdf b/thunderbird/extension/install.rdf new file mode 100644 index 0000000..9b9c0e8 --- /dev/null +++ b/thunderbird/extension/install.rdf @@ -0,0 +1,28 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>thunderbird@zeitgeist-project.com</em:id> + <em:version>0.2</em:version> + <em:type>2</em:type> + + <!-- Target Application this extension can install into, + with minimum and maximum supported versions. --> + <em:targetApplication> + <Description> + <em:id>{3550f703-e582-4d05-9a08-453d09bdfdc6}</em:id> + <em:minVersion>5.0</em:minVersion> + <em:maxVersion>17.*</em:maxVersion> + </Description> + </em:targetApplication> + + <!-- Front End MetaData --> + <em:name>Zeitgeist</em:name> + <em:description>Dataprovider for the zeitgeist framework</em:description> + <em:creator>Mark Tully</em:creator> + <em:homepageURL>http://launchpad.net/zeitgeist</em:homepageURL> + <em:iconURL>chrome://zeitgeist/content/zeitgeist.png</em:iconURL> + </Description> +</RDF> diff --git a/thunderbird/extension/license.txt b/thunderbird/extension/license.txt new file mode 100644 index 0000000..f463a23 --- /dev/null +++ b/thunderbird/extension/license.txt @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/xchat/zeitgeist-dataprovider.c b/xchat/zeitgeist-dataprovider.c index ff54dbe..340f2b1 100644 --- a/xchat/zeitgeist-dataprovider.c +++ b/xchat/zeitgeist-dataprovider.c @@ -77,7 +77,7 @@ static int join_cb(char *word[], void *userdata) channel_list = g_slist_prepend(channel_list, g_strdup(channel)); url = g_strconcat("irc://", server, "/", channel, NULL); - text = g_strconcat("IRC ", channel, NULL); + text = g_strconcat("You joined ", channel, NULL); send_event_to_zeitgeist(url, text, ZEITGEIST_ZG_ACCESS_EVENT); @@ -95,7 +95,7 @@ static int part_cb(char *word[], char* word_eol[], void *userdata) GSList *tmp = channel_list; url = g_strconcat("irc://", server, "/", channel, NULL); - text = g_strconcat("IRC ", channel, NULL); + text = g_strconcat("You parted from ", channel, NULL); send_event_to_zeitgeist(url, text, ZEITGEIST_ZG_LEAVE_EVENT); @@ -116,7 +116,7 @@ static int part_cb(char *word[], char* word_eol[], void *userdata) return XCHAT_EAT_NONE; } -static int message_cb(char *word[], void *userdata) +/*static int message_cb(char *word[], void *userdata) { const char *server = xchat_get_info(ph, "host"); const char *channel = xchat_get_info(ph, "channel"); @@ -131,9 +131,9 @@ static int message_cb(char *word[], void *userdata) g_free(text); return XCHAT_EAT_NONE; -} +}*/ -static int priv_message_cb(char *word[], void *userdata) +/*static int priv_message_cb(char *word[], void *userdata) { const char *server = xchat_get_info(ph, "host"); const char *channel = xchat_get_info(ph, "channel"); @@ -148,7 +148,7 @@ static int priv_message_cb(char *word[], void *userdata) g_free(text); return XCHAT_EAT_NONE; -} +}*/ static void on_quit(gpointer data, gpointer userdata) { @@ -157,7 +157,7 @@ static void on_quit(gpointer data, gpointer userdata) char *url, *text; url = g_strconcat("irc://", server, "/", channel, NULL); - text = g_strconcat("IRC ", channel, NULL); + text = g_strconcat("You parted from ", channel, NULL); send_event_to_zeitgeist(url, text, ZEITGEIST_ZG_LEAVE_EVENT); @@ -200,8 +200,8 @@ int xchat_plugin_init(xchat_plugin *plugin_handle, zg_log = zeitgeist_log_new(); xchat_hook_print(ph, "You Join", XCHAT_PRI_NORM, join_cb, 0); - xchat_hook_print(ph, "Your Message", XCHAT_PRI_NORM, message_cb, 0); - xchat_hook_print(ph, "Channel Msg Hilight", XCHAT_PRI_NORM, priv_message_cb, 0); + /*xchat_hook_print(ph, "Your Message", XCHAT_PRI_NORM, message_cb, 0); + xchat_hook_print(ph, "Channel Msg Hilight", XCHAT_PRI_NORM, priv_message_cb, 0); */ xchat_hook_server(ph, "PART", XCHAT_PRI_NORM, part_cb, 0); xchat_print(ph, "Zeitgeist plugin loaded\n"); |