summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichal Hruby <michal.mhr@gmail.com>2010-07-25 18:51:28 +0200
committerMichal Hruby <michal.mhr@gmail.com>2010-07-25 18:51:28 +0200
commit0772e9744929400169ace94a2fcc1cd0f877beff (patch)
tree32d502d0edefb3571bc770c5f4963d17a276f17c
Initial commit
-rwxr-xr-xcompile.sh4
-rw-r--r--configuration.vala30
-rw-r--r--data-provider.vala51
-rw-r--r--recent-manager-provider.vala259
-rw-r--r--zeitgeist-datahub.vala222
5 files changed, 566 insertions, 0 deletions
diff --git a/compile.sh b/compile.sh
new file mode 100755
index 0000000..f528f0d
--- /dev/null
+++ b/compile.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+valac -g --pkg gtk+-2.0 --pkg zeitgeist-1.0 --pkg dbus-glib-1 zeitgeist-datahub.vala data-provider.vala recent-manager-provider.vala $1
+
diff --git a/configuration.vala b/configuration.vala
new file mode 100644
index 0000000..94b4c09
--- /dev/null
+++ b/configuration.vala
@@ -0,0 +1,30 @@
+/*
+ * Zeitgeist
+ *
+ * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by Michal Hruby <michal.mhr@gmail.com>
+ *
+ */
+
+using Zeitgeist;
+
+[DBus (name = "org.gnome.zeitgeist.datahub")]
+interface DataProviderService : Object
+{
+ public abstract string[] get_data_providers () throws DBus.Error;
+}
+
diff --git a/data-provider.vala b/data-provider.vala
new file mode 100644
index 0000000..3c16b11
--- /dev/null
+++ b/data-provider.vala
@@ -0,0 +1,51 @@
+/*
+ * Zeitgeist
+ *
+ * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by Michal Hruby <michal.mhr@gmail.com>
+ *
+ */
+
+using Zeitgeist;
+
+public abstract class DataProvider : Object
+{
+ public abstract string unique_id { get; construct set; }
+ public abstract string name { get; construct set; }
+ public abstract string description { get; construct set; }
+
+ public abstract DataHub datahub { get; construct set; }
+ public abstract bool enabled { get; set; default = true; }
+ public abstract bool register { get; construct set; default = true; }
+ public int64 last_timestamp { get; set; }
+
+ public virtual void start ()
+ {
+ items_available ();
+ }
+
+ protected abstract List<Event> _get_items ();
+ // DataHub will call get_items when items_available is emitted
+ public List<Event> get_items ()
+ {
+ if (enabled) return _get_items ();
+ return new List<Event> ();
+ }
+
+ public signal void items_available ();
+}
+
diff --git a/recent-manager-provider.vala b/recent-manager-provider.vala
new file mode 100644
index 0000000..277decf
--- /dev/null
+++ b/recent-manager-provider.vala
@@ -0,0 +1,259 @@
+/*
+ * Zeitgeist
+ *
+ * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by Michal Hruby <michal.mhr@gmail.com>
+ *
+ */
+
+using Zeitgeist;
+
+public class RecentManagerGtk : DataProvider
+{
+ public RecentManagerGtk (DataHub datahub)
+ {
+ GLib.Object (unique_id: "com.zeitgeist-project,datahub,recent",
+ name: "Recently Used Documents",
+ description: "Logs events from GtkRecentlyUsed",
+ datahub: datahub);
+ }
+
+ // if vala didn't have bug in construct-only properties, the properties
+ // would be construct-only
+ public override string unique_id { get; construct set; }
+ public override string name { get; construct set; }
+ public override string description { get; construct set; }
+
+ public override DataHub datahub { get; construct set; }
+ public override bool enabled { get; set; default = true; }
+ public override bool register { get; construct set; default = true; }
+
+ private unowned Gtk.RecentManager recent_manager;
+ private HashTable<string, string> app_to_desktop_file;
+ private uint idle_id = 0;
+
+ construct
+ {
+ app_to_desktop_file = new HashTable<string, string> (str_hash, str_equal);
+
+ recent_manager = Gtk.RecentManager.get_default ();
+ recent_manager.set_limit (-1);
+
+ recent_manager.changed.connect (this.items_changed);
+ }
+
+ private void items_changed ()
+ {
+ if (idle_id == 0)
+ {
+ idle_id = Idle.add (() =>
+ {
+ items_available ();
+ idle_id = 0;
+ return false;
+ });
+ }
+ }
+
+ protected override List<Event> _get_items ()
+ {
+ List<Event> events = new List<Event> ();
+
+ if (!enabled) return events;
+
+ int64 signal_time = Timestamp.now ();
+
+ foreach (Gtk.RecentInfo ri in recent_manager.get_items ())
+ {
+ unowned string uri = ri.get_uri ();
+ if (!ri.exists () || ri.get_private_hint () ||
+ uri.has_prefix ("file:///tmp/"))
+ {
+ continue;
+ }
+
+ var last_app = ri.last_application ().strip ();
+ unowned string exec_str;
+ uint count;
+ ulong time_;
+ bool registered = ri.get_application_info (last_app, out exec_str,
+ out count, out time_);
+ if (!registered)
+ {
+ warning ("%s was not registered in RecentInfo item %p", last_app, ri);
+ continue;
+ }
+
+ string[] exec = exec_str.split_set (" \t\n", 2);
+
+ string? desktop_file;
+ if (exec[0] == "soffice" || exec[0] == "ooffice")
+ {
+ // special case OpenOffice... since it must do everything differently
+ desktop_file = get_ooo_desktop_file_for_mimetype (ri.get_mime_type ());
+ }
+ else
+ {
+ desktop_file = find_desktop_file_for_app (exec[0]);
+ }
+
+ if (desktop_file == null)
+ {
+ warning ("Desktop file for %s was not found", exec[0]);
+ continue; // this makes us sad panda
+ }
+
+ var actor = "application://%s".printf (Path.get_basename (desktop_file));
+ unowned string? right_sep = ri.get_uri ().rstr (Path.DIR_SEPARATOR_S);
+ string origin = right_sep != null ?
+ ri.get_uri ().ndup ((char*) right_sep - (char*) ri.get_uri ()) :
+ ri.get_uri ();
+ var subject =
+ new Subject.full (ri.get_uri (),
+ interpretation_for_mimetype (ri.get_mime_type ()),
+ manifestation_for_uri (ri.get_uri ()),
+ ri.get_mime_type (),
+ origin,
+ ri.get_display_name (),
+ ""); // FIXME: storage?!
+
+ Event event;
+
+ // zeitgeist checks for duplicated events, so we can do this
+ event = new Event.full (ZG_CREATE_EVENT,
+ ZG_USER_ACTIVITY,
+ actor,
+ subject, null);
+ event.set_timestamp (ri.get_added () * 1000);
+ if (event.get_timestamp () > last_timestamp)
+ {
+ events.prepend ((owned) event);
+ }
+
+ event = new Event.full (ZG_MODIFY_EVENT,
+ ZG_USER_ACTIVITY,
+ actor,
+ subject, null);
+ event.set_timestamp (ri.get_modified () * 1000);
+ if (event.get_timestamp () > last_timestamp)
+ {
+ events.prepend ((owned) event);
+ }
+
+ event = new Event.full (ZG_ACCESS_EVENT,
+ ZG_USER_ACTIVITY,
+ actor,
+ subject, null);
+ event.set_timestamp (ri.get_visited () * 1000);
+ if (event.get_timestamp () > last_timestamp)
+ {
+ events.prepend ((owned) event);
+ }
+
+ }
+
+ last_timestamp = signal_time;
+
+ events.reverse ();
+ return events;
+ }
+
+ private string? get_ooo_desktop_file_for_mimetype (string mimetype)
+ {
+ return find_desktop_file_for_app ("ooffice", mimetype);
+ }
+
+ private string? find_desktop_file_for_app (string app_name,
+ string? mimetype = null)
+ {
+ string hash_name = mimetype != null ?
+ "%s::%s".printf (app_name, mimetype) : app_name;
+ unowned string? in_cache = app_to_desktop_file.lookup (hash_name);
+ if (in_cache != null)
+ {
+ return in_cache;
+ }
+
+ string[] data_dirs = Environment.get_system_data_dirs ();
+ data_dirs += Environment.get_user_data_dir ();
+
+ foreach (unowned string dir in data_dirs)
+ {
+ var p = Path.build_filename (dir, "applications",
+ "%s.desktop".printf (app_name),
+ null);
+ var f = File.new_for_path (p);
+ if (f.query_exists (null))
+ {
+ app_to_desktop_file.insert (hash_name, p);
+ // FIXME: we're not checking mimetype here!
+ return p;
+ }
+ }
+
+ foreach (unowned string dir in data_dirs)
+ {
+ var p = Path.build_filename (dir, "applications", null);
+ var app_dir = File.new_for_path (p);
+ if (!app_dir.query_exists (null)) continue;
+
+ try
+ {
+ var enumerator =
+ app_dir.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0, null);
+ FileInfo fi = enumerator.next_file (null);
+ while (fi != null)
+ {
+ if (fi.get_name ().has_suffix (".desktop"))
+ {
+ string contents;
+ var desktop_file = Path.build_filename (p, fi.get_name (), null);
+ var f = File.new_for_path (desktop_file);
+ try
+ {
+ if (f.load_contents (null, out contents, null, null))
+ {
+ if ("Exec=%s".printf (app_name) in contents)
+ {
+ if (mimetype == null || mimetype in contents)
+ {
+ app_to_desktop_file.insert (hash_name, desktop_file);
+ return desktop_file;
+ }
+ }
+ }
+ }
+ catch (GLib.Error err)
+ {
+ warning ("%s", err.message);
+ }
+ }
+ fi = enumerator.next_file (null);
+ }
+
+ enumerator.close (null);
+ }
+ catch (GLib.Error err)
+ {
+ warning ("%s", err.message);
+ }
+ }
+
+ return null;
+ }
+}
+
diff --git a/zeitgeist-datahub.vala b/zeitgeist-datahub.vala
new file mode 100644
index 0000000..3a953ef
--- /dev/null
+++ b/zeitgeist-datahub.vala
@@ -0,0 +1,222 @@
+/*
+ * Zeitgeist
+ *
+ * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored by Michal Hruby <michal.mhr@gmail.com>
+ *
+ */
+
+using Zeitgeist;
+
+[DBus (name = "org.gnome.zeitgeist.datahub")]
+interface DataHubService : Object
+{
+ public abstract string[] get_data_providers () throws DBus.Error;
+}
+
+class DataHub : Object, DataHubService
+{
+ private Zeitgeist.Log zg_log;
+ private Zeitgeist.DataSourceRegistry registry;
+ private MainLoop main_loop;
+ private List<DataProvider> providers;
+ private List<DataSource> sources_info; // list got from ZG's Registry
+ private List<Event> queued_events;
+ private uint idle_id = 0;
+
+ public DataHub ()
+ {
+ GLib.Object ();
+ }
+
+ construct
+ {
+ providers = new List<DataProvider> ();
+ sources_info = new List<DataSource> ();
+ queued_events = new List<Event> ();
+ main_loop = new MainLoop ();
+
+ zg_log = new Zeitgeist.Log ();
+ zg_log.notify["connected"].connect (() =>
+ {
+ if (!zg_log.is_connected ())
+ {
+ debug ("Zeitgeist-daemon disappeared from the bus, exitting...");
+ quit ();
+ }
+ });
+
+ registry = new DataSourceRegistry ();
+ start_data_providers ();
+ }
+
+ private async void start_data_providers ()
+ {
+ try
+ {
+ var sources = yield registry.get_data_sources (null);
+ for (uint i=0; i<sources.len; i++)
+ {
+ sources_info.prepend (sources.index (i) as DataSource);
+ }
+ }
+ catch (GLib.Error err)
+ {
+ warning ("%s", err.message);
+ }
+ // TODO: load all datasources once we do them as modules
+ /*
+ foreach (var datasource in datasources)
+ {
+ providers.prepend (datasource.run ());
+ }
+ */
+ providers.prepend (new RecentManagerGtk (this));
+
+ foreach (unowned DataProvider prov in providers)
+ {
+ bool enabled = true;
+ if (prov.register)
+ {
+ var ds = new DataSource.full (prov.unique_id,
+ prov.name,
+ prov.description,
+ new PtrArray ()); // FIXME: templates!
+ try
+ {
+ enabled = yield registry.register_data_source (ds, null);
+ }
+ catch (GLib.Error reg_err)
+ {
+ warning ("%s", reg_err.message);
+ }
+ }
+ prov.items_available.connect (this.items_available);
+ if (enabled)
+ {
+ int64 timestamp = 0;
+ foreach (var src in sources_info)
+ {
+ if (src.get_unique_id () == prov.unique_id)
+ {
+ timestamp = src.get_timestamp ();
+ break;
+ }
+ }
+ prov.last_timestamp = timestamp;
+ prov.start ();
+ }
+ }
+ }
+
+ private void items_available (DataProvider prov)
+ {
+ queued_events.concat (prov.get_items ());
+ if (queued_events != null && idle_id == 0)
+ {
+ idle_id = Idle.add (() =>
+ {
+ insert_events ();
+ idle_id = 0;
+ return false;
+ });
+ }
+ }
+
+ private void insert_events ()
+ {
+ debug ("Inserting %u events", queued_events.length ());
+
+ PtrArray ptr_arr = new PtrArray.with_free_func (Object.unref);
+ // careful here, the ptr array does ref_sink on the events
+ // inside Log.insert_events
+ foreach (unowned Event e in queued_events)
+ {
+ ptr_arr.add (e);
+ }
+
+ zg_log.insert_events_from_ptrarray ((owned) ptr_arr, null,
+ (src, res) =>
+ {
+ unowned Zeitgeist.Log l = src as Zeitgeist.Log;
+ try
+ {
+ l.insert_events_from_ptrarray.end (res);
+ }
+ catch (GLib.Error err)
+ {
+ warning ("Error during inserting events: %s", err.message);
+ }
+ });
+
+ queued_events = new List<Event> ();
+ }
+
+ protected void run ()
+ {
+ try
+ {
+ var connection = DBus.Bus.get (DBus.BusType.SESSION);
+ dynamic DBus.Object bus =
+ connection.get_object ("org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus");
+
+ uint request_name_result =
+ bus.request_name ("org.gnome.zeitgeist.datahub",
+ (uint) DBus.NameFlag.DO_NOT_QUEUE);
+
+ if (request_name_result == DBus.RequestNameReply.PRIMARY_OWNER)
+ {
+ connection.register_object ("/org/gnome/zeitgeist/datahub", this);
+ main_loop.run ();
+ }
+ else
+ {
+ warning ("Unable to get name \"org.gnome.zeitgeist.datahub\"" +
+ " on the bus!");
+ }
+ }
+ catch (GLib.Error err)
+ {
+ warning ("%s", err.message);
+ }
+ }
+
+ protected void quit ()
+ {
+ // dispose all providers
+ providers = new List<DataProvider> ();
+ main_loop.quit ();
+ }
+
+ public string[] get_data_providers () throws DBus.Error
+ {
+ string [] arr = {};
+ foreach (var provider in providers)
+ {
+ arr += provider.unique_id;
+ }
+ return arr;
+ }
+
+ public static void main (string[] args)
+ {
+ var hub = new DataHub ();
+ hub.run ();
+ }
+}