summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSiegfried-Angel Gevatter Pujals <siegfried@gevatter.com>2012-01-27 14:41:09 +0100
committerSiegfried-Angel Gevatter Pujals <siegfried@gevatter.com>2012-01-27 14:41:09 +0100
commit992cb28104753951522c087f0ab922bf0a80e3df (patch)
treec89691dc03f7e51439d063e672f971fb9f211080
parent0df2c0b56c2713a13f7406ca6e326f6c721e9c3d (diff)
parent376b63e66e832fdc2149bd37c81265e22b2d9d53 (diff)
Log events from KDE Recent Document
-rw-r--r--NEWS1
-rw-r--r--src/Makefile.am2
-rw-r--r--src/kde-recent-document-provider.vala229
-rw-r--r--src/recent-manager-provider.vala99
-rw-r--r--src/utils.vala150
-rw-r--r--src/zeitgeist-datahub.vala1
6 files changed, 385 insertions, 97 deletions
diff --git a/NEWS b/NEWS
index 5adb2c6..d62c7e7 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ Zeitgeist-datahub 0.8.0
* Make sure correct desktop environment is set for DesktopAppInfo (Michal Hruby)
* Fix recent-manager-provider to log remote files (Siegfried Gevatter)
+* 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..5957b08 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,7 +15,9 @@ 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 \
$(NULL)
diff --git a/src/kde-recent-document-provider.vala b/src/kde-recent-document-provider.vala
new file mode 100644
index 0000000..fcd91d4
--- /dev/null
+++ b/src/kde-recent-document-provider.vala
@@ -0,0 +1,229 @@
+/*
+ * 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
+{
+ 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);
+ }
+
+ private const string RECENT_DOCUMENTS_PATH =
+ "/.kde/share/apps/RecentDocuments";
+ private const string RECENT_FILE_GROUP = "Desktop Entry";
+
+ private const string ATTRIBUTE_SEPARATOR = ",";
+ private const string FILE_ATTRIBUTE_QUERY_RECENT =
+ GLib.FILE_ATTRIBUTE_STANDARD_TYPE + ATTRIBUTE_SEPARATOR +
+ GLib.FILE_ATTRIBUTE_TIME_MODIFIED + ATTRIBUTE_SEPARATOR +
+ GLib.FILE_ATTRIBUTE_TIME_MODIFIED_USEC;
+ private const string FILE_ATTRIBUTE_QUERY_SUBJECT =
+ GLib.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE + ATTRIBUTE_SEPARATOR +
+ GLib.FILE_ATTRIBUTE_TIME_MODIFIED + ATTRIBUTE_SEPARATOR +
+ GLib.FILE_ATTRIBUTE_TIME_MODIFIED_USEC + ATTRIBUTE_SEPARATOR +
+ GLib.FILE_ATTRIBUTE_TIME_CHANGED + ATTRIBUTE_SEPARATOR +
+ GLib.FILE_ATTRIBUTE_TIME_CHANGED_USEC;
+
+ 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=";
+
+ construct
+ {
+ 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 (
+ FILE_ATTRIBUTE_QUERY_RECENT, GLib.FileQueryInfoFlags.NONE);
+
+ GLib.FileType file_type = (GLib.FileType) recent_info.get_attribute_uint32 (
+ GLib.FILE_ATTRIBUTE_STANDARD_TYPE);
+ if (file_type != GLib.FileType.REGULAR)
+ return null;
+
+ recent_info.get_modification_time (out timeval);
+ int64 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 (
+ FILE_ATTRIBUTE_QUERY_SUBJECT, GLib.FileQueryInfoFlags.NONE);
+
+ subject_info.get_modification_time (out timeval);
+ int64 modification_time = Utils.timeval_to_timestamp (timeval);
+
+ timeval.tv_sec = (long) subject_info.get_attribute_uint64 (
+ GLib.FILE_ATTRIBUTE_TIME_CHANGED);
+ timeval.tv_usec = subject_info.get_attribute_uint32 (
+ GLib.FILE_ATTRIBUTE_TIME_CHANGED_USEC);
+ int64 creation_time = Utils.timeval_to_timestamp (timeval);
+
+ string mimetype = subject_info.get_attribute_string (
+ FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
+
+ string event_interpretation;
+ int64 creation_diff = event_time - creation_time;
+ int64 modification_diff = event_time - modification_time;
+ if (creation_diff.abs () < TIME_EPSILON)
+ event_interpretation = ZG_CREATE_EVENT;
+ else if (modification_diff.abs () < 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 74c3014..29b74c1 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);
}
@@ -112,11 +109,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)
@@ -187,96 +184,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..554f0f6
--- /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 int64 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/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))
{