diff options
author | Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> | 2012-01-27 14:41:09 +0100 |
---|---|---|
committer | Siegfried-Angel Gevatter Pujals <siegfried@gevatter.com> | 2012-01-27 14:41:09 +0100 |
commit | 992cb28104753951522c087f0ab922bf0a80e3df (patch) | |
tree | c89691dc03f7e51439d063e672f971fb9f211080 | |
parent | 0df2c0b56c2713a13f7406ca6e326f6c721e9c3d (diff) | |
parent | 376b63e66e832fdc2149bd37c81265e22b2d9d53 (diff) |
Log events from KDE Recent Document
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/kde-recent-document-provider.vala | 229 | ||||
-rw-r--r-- | src/recent-manager-provider.vala | 99 | ||||
-rw-r--r-- | src/utils.vala | 150 | ||||
-rw-r--r-- | src/zeitgeist-datahub.vala | 1 |
6 files changed, 385 insertions, 97 deletions
@@ -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)) { |