diff options
author | Michal Hruby <michal.mhr@gmail.com> | 2010-07-25 18:51:28 +0200 |
---|---|---|
committer | Michal Hruby <michal.mhr@gmail.com> | 2010-07-25 18:51:28 +0200 |
commit | 0772e9744929400169ace94a2fcc1cd0f877beff (patch) | |
tree | 32d502d0edefb3571bc770c5f4963d17a276f17c |
Initial commit
-rwxr-xr-x | compile.sh | 4 | ||||
-rw-r--r-- | configuration.vala | 30 | ||||
-rw-r--r-- | data-provider.vala | 51 | ||||
-rw-r--r-- | recent-manager-provider.vala | 259 | ||||
-rw-r--r-- | zeitgeist-datahub.vala | 222 |
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 (); + } +} |