From a442dfea1471c95e23fb8bd4b20ad39b146c243f Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 29 Nov 2009 17:35:35 -0500 Subject: [PATCH] Add class for handling recent docs Push some of the JS docInfo down into C; crucially, this lets us use the GIO async API. https://bugzilla.gnome.org/show_bug.cgi?id=603522 --- src/Makefile.am | 2 + src/shell-doc-system.c | 362 +++++++++++++++++++++++++++++++++++++++++ src/shell-doc-system.h | 46 ++++++ 3 files changed, 410 insertions(+) create mode 100644 src/shell-doc-system.c create mode 100644 src/shell-doc-system.h diff --git a/src/Makefile.am b/src/Makefile.am index 29c36cb80..0b9ea5e9d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -63,6 +63,8 @@ libgnome_shell_la_SOURCES = \ shell-app-usage.h \ shell-arrow.c \ shell-arrow.h \ + shell-doc-system.c \ + shell-doc-system.h \ shell-drawing.c \ shell-drawing.h \ shell-embedded-window.c \ diff --git a/src/shell-doc-system.c b/src/shell-doc-system.c new file mode 100644 index 000000000..c58b34b1b --- /dev/null +++ b/src/shell-doc-system.c @@ -0,0 +1,362 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +#include "config.h" + +#include "shell-doc-system.h" + +#include "shell-global.h" +#include "shell-texture-cache.h" + + +/** + * SECTION:shell-doc-system + * @short_description: Track recently used documents + * + * Wraps #GtkRecentManager, caching recently used document information, and adds + * APIs for asynchronous queries. + */ +enum { + CHANGED, + DELETED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0 }; + +struct _ShellDocSystemPrivate { + GtkRecentManager *manager; + GHashTable *infos_by_uri; + GSList *infos_by_timestamp; + + guint idle_recent_changed_id; + + GHashTable *deleted_infos; + guint idle_emit_deleted_id; +}; + +G_DEFINE_TYPE(ShellDocSystem, shell_doc_system, G_TYPE_OBJECT); + +/** + * shell_doc_system_get_all: + * @self: A #ShellDocSystem + * + * Returns the currently cached set of recent files. Recent files are read initially + * from the underlying #GtkRecentManager, and updated when it changes. + * This function does not perform I/O. + * + * Returns: (transfer none) (element-type GtkRecentInfo): Cached recent file infos + */ +GSList * +shell_doc_system_get_all (ShellDocSystem *self) +{ + return self->priv->infos_by_timestamp; +} + +/** + * @self: A #ShellDocSystem + * @uri: Url + * + * Returns: (transfer none): Recent file info corresponding to given @uri + */ +GtkRecentInfo * +shell_doc_system_lookup_by_uri (ShellDocSystem *self, + const char *uri) +{ + return g_hash_table_lookup (self->priv->infos_by_uri, uri); +} + +static gboolean +shell_doc_system_idle_emit_deleted (gpointer data) +{ + ShellDocSystem *self = SHELL_DOC_SYSTEM (data); + GHashTableIter iter; + gpointer key, value; + + self->priv->idle_emit_deleted_id = 0; + + g_hash_table_iter_init (&iter, self->priv->deleted_infos); + + while (g_hash_table_iter_next (&iter, &key, &value)) + { + GtkRecentInfo *info = key; + g_signal_emit (self, signals[DELETED], 0, info); + } + + g_signal_emit (self, signals[CHANGED], 0); + + return FALSE; +} + +typedef struct { + ShellDocSystem *self; + GtkRecentInfo *info; +} ShellDocSystemRecentQueryData; + +static void +on_recent_file_query_result (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + ShellDocSystemRecentQueryData *data = user_data; + ShellDocSystem *self = data->self; + GError *error = NULL; + GFileInfo *fileinfo; + + fileinfo = g_file_query_info_finish (G_FILE (source), result, &error); + if (fileinfo) + g_object_unref (fileinfo); + /* This is a strict error check; we don't want to cause recent files to + * vanish for anything potentially transient. + */ + if (error != NULL && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND) + { + self->priv->infos_by_timestamp = g_slist_remove (self->priv->infos_by_timestamp, data->info); + g_hash_table_remove (self->priv->infos_by_uri, gtk_recent_info_get_uri (data->info)); + + g_hash_table_insert (self->priv->deleted_infos, gtk_recent_info_ref (data->info), NULL); + + if (self->priv->idle_emit_deleted_id == 0) + self->priv->idle_emit_deleted_id = g_timeout_add (0, shell_doc_system_idle_emit_deleted, self); + } + g_clear_error (&error); + + gtk_recent_info_unref (data->info); + g_free (data); +} + +/** + * shell_doc_system_queue_existence_check: + * @self: A #ShellDocSystem + * @n_items: Count of items to check for existence, starting from most recent + * + * Asynchronously start a check of a number of recent file for existence; + * any deleted files will be emitted from the #ShellDocSystem::deleted + * signal. Note that this function ignores non-local files; they + * will simply always appear to exist (until they are removed from + * the recent file list manually). + * + * The intent of this function is to be called after a #ShellDocSystem::changed + * signal has been emitted, and a display has shown a subset of those files. + */ +void +shell_doc_system_queue_existence_check (ShellDocSystem *self, + guint n_items) +{ + GSList *iter; + guint i; + + for (i = 0, iter = self->priv->infos_by_timestamp; i < n_items && iter; i++, iter = iter->next) + { + GtkRecentInfo *info = iter->data; + const char *uri; + GFile *file; + ShellDocSystemRecentQueryData *data; + + if (!gtk_recent_info_is_local (info)) + continue; + + data = g_new0 (ShellDocSystemRecentQueryData, 1); + data->self = self; + data->info = gtk_recent_info_ref (info); + + uri = gtk_recent_info_get_uri (info); + file = g_file_new_for_uri (uri); + + g_file_query_info_async (file, "standard::type", G_FILE_QUERY_INFO_NONE, + G_PRIORITY_DEFAULT, NULL, on_recent_file_query_result, data); + g_object_unref (file); + } +} + +static int +sort_infos_by_timestamp_descending (gconstpointer a, + gconstpointer b) +{ + GtkRecentInfo *info_a = (GtkRecentInfo*)a; + GtkRecentInfo *info_b = (GtkRecentInfo*)b; + time_t modified_a, modified_b; + + modified_a = gtk_recent_info_get_modified (info_a); + modified_b = gtk_recent_info_get_modified (info_b); + + return modified_b - modified_a; +} + +static gboolean +idle_handle_recent_changed (gpointer data) +{ + ShellDocSystem *self = SHELL_DOC_SYSTEM (data); + GList *items, *iter; + + self->priv->idle_recent_changed_id = 0; + + g_hash_table_remove_all (self->priv->deleted_infos); + g_hash_table_remove_all (self->priv->infos_by_uri); + g_slist_free (self->priv->infos_by_timestamp); + self->priv->infos_by_timestamp = NULL; + + items = gtk_recent_manager_get_items (self->priv->manager); + for (iter = items; iter; iter = iter->next) + { + GtkRecentInfo *info = iter->data; + const char *uri = gtk_recent_info_get_uri (info); + + /* uri is owned by the info */ + g_hash_table_insert (self->priv->infos_by_uri, (char*) uri, info); + + self->priv->infos_by_timestamp = g_slist_prepend (self->priv->infos_by_timestamp, info); + } + g_list_free (items); + + self->priv->infos_by_timestamp = g_slist_sort (self->priv->infos_by_timestamp, sort_infos_by_timestamp_descending); + + g_signal_emit (self, signals[CHANGED], 0); + + return FALSE; +} + +static void +shell_doc_system_on_recent_changed (GtkRecentManager *manager, + ShellDocSystem *self) +{ + if (self->priv->idle_recent_changed_id != 0) + return; + self->priv->idle_recent_changed_id = g_timeout_add (0, idle_handle_recent_changed, self); +} + +/** + * shell_doc_system_open: + * @system: A #ShellDocSystem + * @info: A #GtkRecentInfo + * + * Launch the default application associated with the mime type of + * @info, using its uri. + */ +void +shell_doc_system_open (ShellDocSystem *system, + GtkRecentInfo *info) +{ + GFile *file; + GAppInfo *app_info; + gboolean needs_uri; + + file = g_file_new_for_uri (gtk_recent_info_get_uri (info)); + needs_uri = g_file_get_path (file) == NULL; + g_object_unref (file); + + app_info = g_app_info_get_default_for_type (gtk_recent_info_get_mime_type (info), needs_uri); + if (app_info != NULL) + { + GList *uris; + uris = g_list_prepend (NULL, (gpointer)gtk_recent_info_get_uri (info)); + g_app_info_launch_uris (app_info, uris, shell_global_create_app_launch_context (shell_global_get ()), NULL); + g_list_free (uris); + } + else + { + char *app_name; + char *app_exec, *app_exec_quoted; + guint count; + time_t time; + + app_name = gtk_recent_info_last_application (info); + if (gtk_recent_info_get_application_info (info, app_name, &app_exec, &count, &time)) + { + GRegex *regex; + GAppLaunchContext *context; + + /* TODO: Change this once better support for creating + GAppInfo is added to GtkRecentInfo, as right now + this relies on the fact that the file uri is + already a part of appExec, so we don't supply any + files to app_info.launch(). + + The 'command line' passed to + create_from_command_line is allowed to contain + '%' macros that are expanded to file + name / icon name, etc, so we need to escape % as %% + */ + + regex = g_regex_new ("%", 0, 0, NULL); + app_exec_quoted = g_regex_replace (regex, app_exec, -1, 0, "%%", 0, NULL); + g_regex_unref (regex); + + app_info = g_app_info_create_from_commandline (app_exec, NULL, 0, NULL); + + /* The point of passing an app launch context to + launch() is mostly to get startup notification and + associated benefits like the app appearing on the + right desktop; but it doesn't really work for now + because with the way we create the appInfo we + aren't reading the application's desktop file, and + thus don't find the StartupNotify=true in it. So, + despite passing the app launch context, no startup + notification occurs. + */ + context = shell_global_create_app_launch_context (shell_global_get ()); + g_app_info_launch (app_info, NULL, context, NULL); + g_object_unref (context); + } + + g_free (app_name); + } +} + +static void +shell_doc_system_class_init(ShellDocSystemClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *)klass; + + signals[CHANGED] = + g_signal_new ("changed", + SHELL_TYPE_DOC_SYSTEM, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[DELETED] = + g_signal_new ("deleted", + SHELL_TYPE_DOC_SYSTEM, + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, GTK_TYPE_RECENT_INFO); + + g_type_class_add_private (gobject_class, sizeof (ShellDocSystemPrivate)); +} + +static void +shell_doc_system_init (ShellDocSystem *self) +{ + ShellDocSystemPrivate *priv; + + self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + SHELL_TYPE_DOC_SYSTEM, + ShellDocSystemPrivate); + self->priv->manager = gtk_recent_manager_get_default (); + + self->priv->deleted_infos = g_hash_table_new_full (NULL, NULL, (GDestroyNotify)gtk_recent_info_unref, NULL); + self->priv->infos_by_uri = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)gtk_recent_info_unref); + + g_signal_connect (self->priv->manager, "changed", G_CALLBACK(shell_doc_system_on_recent_changed), self); + shell_doc_system_on_recent_changed (self->priv->manager, self); +} + +/** + * shell_doc_system_get_default: + * + * Return Value: (transfer none): The global #ShellDocSystem singleton + */ +ShellDocSystem * +shell_doc_system_get_default () +{ + static ShellDocSystem *instance = NULL; + + if (instance == NULL) + instance = g_object_new (SHELL_TYPE_DOC_SYSTEM, NULL); + + return instance; +} diff --git a/src/shell-doc-system.h b/src/shell-doc-system.h new file mode 100644 index 000000000..0f2ad6037 --- /dev/null +++ b/src/shell-doc-system.h @@ -0,0 +1,46 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +#ifndef __SHELL_DOC_SYSTEM_H__ +#define __SHELL_DOC_SYSTEM_H__ + +#include +#include + +#define SHELL_TYPE_DOC_SYSTEM (shell_doc_system_get_type ()) +#define SHELL_DOC_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_DOC_SYSTEM, ShellDocSystem)) +#define SHELL_DOC_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_DOC_SYSTEM, ShellDocSystemClass)) +#define SHELL_IS_DOC_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_DOC_SYSTEM)) +#define SHELL_IS_DOC_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_DOC_SYSTEM)) +#define SHELL_DOC_SYSTEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_DOC_SYSTEM, ShellDocSystemClass)) + +typedef struct _ShellDocSystem ShellDocSystem; +typedef struct _ShellDocSystemClass ShellDocSystemClass; +typedef struct _ShellDocSystemPrivate ShellDocSystemPrivate; + +struct _ShellDocSystem +{ + GObject parent; + + ShellDocSystemPrivate *priv; +}; + +struct _ShellDocSystemClass +{ + GObjectClass parent_class; +}; + +GType shell_doc_system_get_type (void) G_GNUC_CONST; + +ShellDocSystem* shell_doc_system_get_default (void); + +GSList *shell_doc_system_get_all (ShellDocSystem *system); + +GtkRecentInfo *shell_doc_system_lookup_by_uri (ShellDocSystem *system, + const char *uri); + +void shell_doc_system_queue_existence_check (ShellDocSystem *system, + guint n_recent); + +void shell_doc_system_open (ShellDocSystem *system, + GtkRecentInfo *info); + +#endif /* __SHELL_DOC_SYSTEM_H__ */