summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiegfried-Angel Gevatter Pujals <siegfried@gevatter.com>2012-01-27 12:37:25 +0100
committerSiegfried-Angel Gevatter Pujals <siegfried@gevatter.com>2012-01-27 12:37:25 +0100
commit376b63e66e832fdc2149bd37c81265e22b2d9d53 (patch)
treeefc07e56b6ff65f07d6d6477fdf0ac51f1d747a5
parentede92dfcbe4fbe2568e564499af2f2c0dae72e79 (diff)
Log events from KDE Recent Document
-rw-r--r--NEWS1
-rw-r--r--src/Makefile.am3
-rw-r--r--src/kde-recent-document-provider.vala213
-rw-r--r--src/recent-manager-provider.vala99
-rw-r--r--src/utils.vala150
-rw-r--r--src/wrapper.vapi2
-rw-r--r--src/zeitgeist-datahub.vala1
7 files changed, 372 insertions, 97 deletions
diff --git a/NEWS b/NEWS
index 4f43cdc..3eff8fe 100644
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@ Zeitgeist-datahub 0.8.0
=======================
* Make sure correct desktop environment is set for DesktopAppInfo (Michal Hruby)
+* Log events from KDE Recent Document (Siegfried Gevatter)
Zeitgeist-datahub 0.7.0
=======================
diff --git a/src/Makefile.am b/src/Makefile.am
index ff5c7db..f821e63 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,8 +15,11 @@ zeitgeist_datahub_LDADD = $(DATAHUB_MODULES_LIBS) $(GTK_LIBS)
zeitgeist_datahub_SOURCES = \
data-provider.vala \
desktop-launch-listener.vala \
+ kde-recent-document-provider.vala \
recent-manager-provider.vala \
+ utils.vala \
zeitgeist-datahub.vala \
+ wrapper.vapi \
$(NULL)
xdgautostart_in_files = \
diff --git a/src/kde-recent-document-provider.vala b/src/kde-recent-document-provider.vala
new file mode 100644
index 0000000..9fb7393
--- /dev/null
+++ b/src/kde-recent-document-provider.vala
@@ -0,0 +1,213 @@
+/*
+ * Zeitgeist
+ *
+ * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
+ * Copyright (C) 2012 Canonical Ltd.
+ *
+ * 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>
+ * Authored by Siegfried-A. Gevatter <siegfried.gevatter@collabora.co.uk>
+ *
+ */
+
+using Zeitgeist;
+
+public class RecentDocumentsKDE : DataProvider
+{
+ private const string RECENT_DOCUMENTS_PATH =
+ "/.kde/share/apps/RecentDocuments";
+ private const string RECENT_FILE_GROUP = "Desktop Entry";
+
+ private const int TIME_EPSILON = 100; // msec
+
+ // 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 string recent_document_path;
+ private GLib.File recent_documents_directory;
+ private GLib.FileMonitor monitor;
+ private string[] ignored_actors;
+
+ private GLib.Regex recent_regex;
+ private GLib.Regex url_regex;
+ private const string RECENT_REGEX_REPLACEMENT = "URL=";
+
+ public RecentDocumentsKDE (DataHub datahub) throws GLib.Error
+ {
+ GLib.Object (unique_id: "com.zeitgeist-project,datahub,kde-recent",
+ name: "Recently Used Documents (KDE)",
+ description: "Logs events from KRecentDocument",
+ datahub: datahub);
+
+ recent_regex = new Regex ("URL\\[[^]]+\\]=");
+ url_regex = new Regex ("\\$HOME");
+
+ recent_document_path = Environment.get_home_dir () + RECENT_DOCUMENTS_PATH;
+ recent_documents_directory = File.new_for_path (recent_document_path);
+ monitor = recent_documents_directory.monitor_directory (
+ GLib.FileMonitorFlags.NONE);
+ }
+
+ public override void start ()
+ {
+ ignored_actors = datahub.get_data_source_actors ();
+ monitor.changed.connect (this.process_event);
+
+ crawl_all_items ();
+ }
+
+ public override void stop ()
+ {
+ monitor.changed.disconnect (this.process_event);
+ }
+
+ private async void process_event (GLib.File file, GLib.File? other_file,
+ GLib.FileMonitorEvent event_type)
+ {
+ if (event_type == GLib.FileMonitorEvent.CREATED ||
+ event_type == GLib.FileMonitorEvent.CHANGED ||
+ event_type == GLib.FileMonitorEvent.ATTRIBUTE_CHANGED)
+ {
+ try
+ {
+ Event? event = yield parse_file (file);
+ if (event != null)
+ {
+ GenericArray<Event> events = new GenericArray<Event> ();
+ events.add ((owned) event);
+ items_available (events);
+ }
+ }
+ catch (GLib.Error err)
+ {
+ warning ("Couldn't process %s: %s", file.get_path (), err.message);
+ }
+ }
+ }
+
+ private async Event? parse_file (GLib.File file) throws GLib.Error
+ {
+ TimeVal timeval;
+
+ if (!file.get_basename ().has_suffix (".desktop"))
+ return null;
+
+ var recent_info = yield file.query_info_async (
+ "standard::type,time::modified,time::modified-usec",
+ GLib.FileQueryInfoFlags.NONE);
+
+ GLib.FileType file_type = (GLib.FileType) recent_info.get_attribute_uint32 (
+ "standard::type");
+ if (file_type != GLib.FileType.REGULAR)
+ return null;
+
+ recent_info.get_modification_time (out timeval);
+ long event_time = Utils.timeval_to_timestamp (timeval);
+
+ string? content = Utils.get_file_contents (file);
+ if (content == null)
+ return null;
+ content = recent_regex.replace (content, content.length, 0,
+ RECENT_REGEX_REPLACEMENT);
+
+ KeyFile recent_file = new KeyFile ();
+ recent_file.load_from_data (content, content.length, KeyFileFlags.NONE);
+ string basename = recent_file.get_string (RECENT_FILE_GROUP, "Name");
+ string uri = recent_file.get_string (RECENT_FILE_GROUP, "URL");
+ string desktop_entry_name = recent_file.get_string (RECENT_FILE_GROUP,
+ "X-KDE-LastOpenedWith");
+
+ // URL may contain environment variables. In practice, KConfigGroup
+ // only uses $HOME.
+ uri = url_regex.replace (uri, uri.length, 0, Environment.get_home_dir ());
+
+ string actor = "application://%s.desktop".printf (desktop_entry_name);
+ if (actor in ignored_actors)
+ return null;
+
+ GLib.File subject_file = File.new_for_uri (uri);
+ var subject_info = subject_file.query_info (
+ "standard::content-type,time::modified,time::modified-usec," +
+ "time::changed,time::changed-usec",
+ GLib.FileQueryInfoFlags.NONE);
+
+ subject_info.get_modification_time (out timeval);
+ long modification_time = Utils.timeval_to_timestamp (timeval);
+
+ timeval.tv_sec = (long) subject_info.get_attribute_uint64 ("time::changed");
+ timeval.tv_usec = subject_info.get_attribute_uint32 ("time::changed-usec");
+ long creation_time = Utils.timeval_to_timestamp (timeval);
+
+ string mimetype = subject_info.get_attribute_string (
+ FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+
+ string event_interpretation;
+ if (abs (event_time - creation_time) < TIME_EPSILON)
+ event_interpretation = ZG_CREATE_EVENT;
+ else if (abs (event_time - modification_time) < TIME_EPSILON)
+ event_interpretation = ZG_MODIFY_EVENT;
+ else
+ event_interpretation = ZG_ACCESS_EVENT;
+
+ string origin = Path.get_dirname (uri);
+ var subject =
+ new Subject.full (uri,
+ interpretation_for_mimetype (mimetype),
+ manifestation_for_uri (uri),
+ mimetype,
+ origin,
+ basename,
+ ""); // storage will be figured out by Zeitgeist
+
+ Event event = new Event.full (event_interpretation, ZG_USER_ACTIVITY,
+ actor, subject, null);
+ event.set_timestamp (event_time);
+
+ return event;
+ }
+
+ protected async void crawl_all_items () throws GLib.FileError
+ {
+ string filename;
+ GenericArray<Event> events = new GenericArray<Event> ();
+
+ GLib.Dir directory = GLib.Dir.open (recent_document_path);
+ while ((filename = directory.read_name ()) != null)
+ {
+ var file = GLib.File.new_for_path (
+ recent_document_path + "/" + filename);
+ try
+ {
+ Event? event = yield parse_file (file);
+ if (event != null)
+ events.add ((owned) event);
+ }
+ catch (GLib.Error err)
+ {
+ // Silently ignore. The files may be gone by now - who cares?
+ }
+ }
+
+ // Zeitgeist will take care of ignoring the duplicates
+ items_available (events);
+ }
+}
diff --git a/src/recent-manager-provider.vala b/src/recent-manager-provider.vala
index 3fe7ce4..ceb2244 100644
--- a/src/recent-manager-provider.vala
+++ b/src/recent-manager-provider.vala
@@ -43,13 +43,10 @@ public class RecentManagerGtk : DataProvider
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);
}
@@ -113,11 +110,11 @@ public class RecentManagerGtk : DataProvider
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 ());
+ desktop_file = Utils.get_ooo_desktop_file_for_mimetype (ri.get_mime_type ());
}
else
{
- desktop_file = find_desktop_file_for_app (exec[0]);
+ desktop_file = Utils.find_desktop_file_for_app (exec[0]);
}
if (desktop_file == null)
@@ -188,96 +185,4 @@ public class RecentManagerGtk : DataProvider
return events;
}
-
- private string? get_ooo_desktop_file_for_mimetype (string mimetype)
- {
- return find_desktop_file_for_app ("libreoffice", mimetype) ??
- 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"))
- {
- var desktop_file = Path.build_filename (p, fi.get_name (), null);
- var f = File.new_for_path (desktop_file);
- try
- {
-#if VALA_0_14
- uint8[] contents_array;
- if (f.load_contents (null, out contents_array, null))
- {
- unowned string contents = (string) contents_array;
-#else
- string contents;
- if (f.load_contents (null, out contents, null, null))
- {
-#endif
- 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/src/utils.vala b/src/utils.vala
new file mode 100644
index 0000000..f89f7dc
--- /dev/null
+++ b/src/utils.vala
@@ -0,0 +1,150 @@
+/*
+ * Zeitgeist
+ *
+ * Copyright (C) 2010 Michal Hruby <michal.mhr@gmail.com>
+ * Copyright (C) 2012 Canonical Ltd.
+ *
+ * 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>
+ * Authored by Siegfried-A. Gevatter <siegfried.gevatter@collabora.co.uk>
+ *
+ */
+
+using Zeitgeist;
+
+public class Utils : Object
+{
+ private static HashTable<string, string> app_to_desktop_file = null;
+
+ // FIXME: Do we want to make this async?
+ // FIXME: this can throw GLib.Error, but if we use try/catch or throws
+ // it makes segfaults :(
+ public static string? get_file_contents (GLib.File file)
+ {
+ string contents;
+#if VALA_0_14
+ uint8[] contents_array;
+ if (!file.load_contents (null, out contents_array, null))
+ return null;
+ contents = (string) contents_array;
+#else
+ if (!file.load_contents (null, out contents, null, null))
+ return null;
+#endif
+ return (owned) contents;
+ }
+
+ /*
+ * Takes a TimeVal and returns a Zeitgeist timestamp (ie. timestamp in msec).
+ *
+ * */
+ public static long timeval_to_timestamp (TimeVal timeval)
+ {
+ return (timeval.tv_sec * 1000) + (timeval.tv_usec / 1000);
+ }
+
+ private static void init ()
+ {
+ if (unlikely (app_to_desktop_file == null))
+ app_to_desktop_file = new HashTable<string, string> (str_hash, str_equal);
+ }
+
+ public static string? get_ooo_desktop_file_for_mimetype (string mimetype)
+ {
+ return find_desktop_file_for_app ("libreoffice", mimetype) ??
+ find_desktop_file_for_app ("ooffice", mimetype);
+ }
+
+ public static string? find_desktop_file_for_app (string app_name,
+ string? mimetype = null)
+ {
+ init ();
+
+ 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"))
+ {
+ var desktop_file = Path.build_filename (p, fi.get_name (), null);
+ var f = File.new_for_path (desktop_file);
+ try
+ {
+ string? contents = Utils.get_file_contents (f);
+ if (contents != 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/src/wrapper.vapi b/src/wrapper.vapi
new file mode 100644
index 0000000..29a883e
--- /dev/null
+++ b/src/wrapper.vapi
@@ -0,0 +1,2 @@
+[CCode (cname = "abs", cheader_filename = "stdlib.h")]
+public long abs (long i);
diff --git a/src/zeitgeist-datahub.vala b/src/zeitgeist-datahub.vala
index d40a7b8..7779fbc 100644
--- a/src/zeitgeist-datahub.vala
+++ b/src/zeitgeist-datahub.vala
@@ -110,6 +110,7 @@ public class DataHub : Object, DataHubService
}
*/
providers.prepend (new RecentManagerGtk (this));
+ providers.prepend (new RecentDocumentsKDE (this));
if (GLibExtra.check_version (2, 28, 0))
{