]> Pileus Git - ~andy/gtk/commitdiff
Bug 487375 – gtkrecent apps poll ~/.recently-used.xbel every 5 seconds
authorEmmanuele Bassi <ebassi@gnome.org>
Tue, 15 Apr 2008 22:22:44 +0000 (22:22 +0000)
committerEmmanuele Bassi <ebassi@src.gnome.org>
Tue, 15 Apr 2008 22:22:44 +0000 (22:22 +0000)
2008-04-15  Emmanuele Bassi  <ebassi@gnome.org>

Bug 487375 – gtkrecent apps poll ~/.recently-used.xbel
every 5 seconds

* gtk/gtkrecentmanager.c: Use GFileMonitor to check for
changes in the recently used resources storage file, and
drop the timeout-based poll.

* ChangeLog: Fix attribution of the patch of the previous
commit.

svn path=/trunk/; revision=20004

ChangeLog
gtk/gtkrecentmanager.c

index 9d4f11ff9f4cbddf63c5c81dfa63ae6565f343b7..a205dbd939178d12c7d1178ee45ce439e2a88ce0 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2008-04-15  Emmanuele Bassi  <ebassi@gnome.org>
+
+       Bug 487375 – gtkrecent apps poll ~/.recently-used.xbel
+       every 5 seconds
+
+       * gtk/gtkrecentmanager.c: Use GFileMonitor to check for
+       changes in the recently used resources storage file, and
+       drop the timeout-based poll.
+
+       * ChangeLog: Fix attribution of the patch of the previous
+       commit.
+
 2008-04-15  Emmanuele Bassi  <ebassi@gnome.org>
 
        Bug 506062 – gtk_recent_manager_add_item does not detect mime
@@ -7,8 +19,9 @@
 
        * gtk/gtkrecentmanager.c:
        (gtk_recent_manager_add_item_query_info_cb),
-       (gtk_recent_manager_add_item): Use GIO to (asynchronously) query
-       the MIME type of the passed URI.
+       (gtk_recent_manager_add_item): Use GIO to (asynchronously)
+       query the MIME type of the passed URI (based on a patch by
+       Armin Burgmeier)
 
 2008-04-11  Carlos Garnacho  <carlos@imendio.com>D:ChangeLog
 
index af6dba15308b5d20a41e8f8f772f3cb52cef409d..9d2b23f1329f828395da1abeb0e7e4f2563d840a 100644 (file)
 /* the file where we store the recently used items */
 #define GTK_RECENTLY_USED_FILE ".recently-used.xbel"
 
-/* a poll approximately every five seconds */
-#define POLL_DELTA      5
-
 /* return all items by default */
 #define DEFAULT_LIMIT  -1
 
 /* keep in sync with xdgmime */
 #define GTK_RECENT_DEFAULT_MIME        "application/octet-stream"
 
-typedef struct
-{
-  GSourceFunc func;
-  gpointer data;
-  GDestroyNotify notify;
-} ThreadsDispatch;
-
 typedef struct
 {
   gchar *name;
@@ -100,21 +90,20 @@ struct _GtkRecentInfo
   gint ref_count;
 };
 
+#define GTK_RECENT_MANAGER_GET_PRIVATE(obj)     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RECENT_MANAGER, GtkRecentManagerPrivate))
+
 struct _GtkRecentManagerPrivate
 {
   gchar *filename;
 
   guint is_dirty : 1;
-  guint write_in_progress : 1;
-  guint read_in_progress : 1;
   
   gint limit;
   gint size;
 
   GBookmarkFile *recent_items;
-  
-  time_t last_mtime;
-  guint poll_timeout;
+
+  GFileMonitor *monitor;
 };
 
 enum
@@ -126,36 +115,38 @@ enum
   PROP_SIZE
 };
 
-static void           gtk_recent_manager_dispose      (GObject               *object);
-static void           gtk_recent_manager_finalize     (GObject               *object);
-
-static void           gtk_recent_manager_set_property (GObject               *object,
-                                                      guint                  prop_id,
-                                                      const GValue          *value,
-                                                      GParamSpec            *pspec);
-static void           gtk_recent_manager_get_property (GObject               *object,
-                                                      guint                  prop_id,
-                                                      GValue                *value,
-                                                      GParamSpec            *pspec);
-static void gtk_recent_manager_add_item_query_info_cb (GObject              *source_object,
-                                                       GAsyncResult         *res,
-                                                       gpointer             user_data);
-static void           gtk_recent_manager_changed      (GtkRecentManager      *manager);
-
-static void           gtk_recent_manager_real_changed (GtkRecentManager      *manager);
-static gboolean       gtk_recent_manager_poll_timeout (gpointer               data);
-static void           gtk_recent_manager_set_filename (GtkRecentManager      *manager,
-                                                      const gchar           *filename);
-
-static void           build_recent_items_list         (GtkRecentManager      *manager);
-static void           purge_recent_items_list         (GtkRecentManager      *manager,
-                                                      GError               **error);
-
-static RecentAppInfo *recent_app_info_new             (const gchar           *app_name);
-static void           recent_app_info_free            (RecentAppInfo         *app_info);
-
-static GtkRecentInfo *gtk_recent_info_new             (const gchar           *uri);
-static void           gtk_recent_info_free            (GtkRecentInfo         *recent_info);
+static void     gtk_recent_manager_dispose                (GObject           *object);
+static void     gtk_recent_manager_finalize               (GObject           *object);
+static void     gtk_recent_manager_set_property           (GObject           *object,
+                                                          guint              prop_id,
+                                                          const GValue      *value,
+                                                          GParamSpec        *pspec);
+static void     gtk_recent_manager_get_property           (GObject           *object,
+                                                          guint              prop_id,
+                                                          GValue            *value,
+                                                          GParamSpec        *pspec);
+static void     gtk_recent_manager_add_item_query_info_cb (GObject           *source_object,
+                                                           GAsyncResult      *res,
+                                                           gpointer           user_data);
+static void     gtk_recent_manager_monitor_changed        (GFileMonitor      *monitor,
+                                                           GFile             *file,
+                                                           GFile             *other_file,
+                                                           GFileMonitorEvent  event_type,
+                                                           gpointer           user_data);
+static void     gtk_recent_manager_changed                (GtkRecentManager  *manager);
+static void     gtk_recent_manager_real_changed           (GtkRecentManager  *manager);
+static void     gtk_recent_manager_set_filename           (GtkRecentManager  *manager,
+                                                           const gchar       *filename);
+
+static void build_recent_items_list (GtkRecentManager  *manager);
+static void purge_recent_items_list (GtkRecentManager  *manager,
+                                     GError           **error);
+
+static RecentAppInfo *recent_app_info_new  (const gchar   *app_name);
+static void           recent_app_info_free (RecentAppInfo *app_info);
+
+static GtkRecentInfo *gtk_recent_info_new  (const gchar   *uri);
+static void           gtk_recent_info_free (GtkRecentInfo *recent_info);
 
 static guint signal_changed = 0;
 
@@ -201,40 +192,11 @@ gtk_recent_manager_error_quark (void)
   return g_quark_from_static_string ("gtk-recent-manager-error-quark");
 }
 
-static gboolean
-threads_dispatch (gpointer data)
-{
-  ThreadsDispatch *dispatch = data;
-  gboolean res = FALSE;
-
-  GDK_THREADS_ENTER ();
-
-  if (!g_source_is_destroyed (g_main_current_source ()))
-    res = dispatch->func (dispatch->data);
-
-  GDK_THREADS_LEAVE ();
-
-  return res;
-}
-
-static void
-threads_free (gpointer data)
-{
-  ThreadsDispatch *dispatch = data;
-
-  if (dispatch->notify)
-    dispatch->notify (dispatch->data);
-
-  g_slice_free (ThreadsDispatch, dispatch);
-}
-
 static void
 gtk_recent_manager_class_init (GtkRecentManagerClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   
-  gtk_recent_manager_parent_class = g_type_class_peek_parent (klass);
-  
   gobject_class->set_property = gtk_recent_manager_set_property;
   gobject_class->get_property = gtk_recent_manager_get_property;
   gobject_class->dispose = gtk_recent_manager_dispose;
@@ -254,7 +216,7 @@ gtk_recent_manager_class_init (GtkRecentManagerClass *klass)
                                                        P_("Filename"),
                                                        P_("The full path to the file to be used to store and read the list"),
                                                        NULL,
-                                                       (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READABLE | G_PARAM_WRITABLE)));
+                                                       (G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE)));
   /**
    * GtkRecentManager:limit
    *
@@ -316,34 +278,19 @@ static void
 gtk_recent_manager_init (GtkRecentManager *manager)
 {
   GtkRecentManagerPrivate *priv;
-  ThreadsDispatch *dispatch;
-  
-  priv = g_type_instance_get_private ((GTypeInstance *) manager,
-                                     GTK_TYPE_RECENT_MANAGER);
-  manager->priv = priv;
+  gchar *filename;
+
+  manager->priv = priv = GTK_RECENT_MANAGER_GET_PRIVATE (manager);
   
   priv->limit = DEFAULT_LIMIT;
   priv->size = 0;
-  
-  priv->is_dirty = FALSE;
-  priv->write_in_progress = FALSE;
-  priv->read_in_progress = FALSE;
-
-  priv->filename = g_build_filename (g_get_home_dir (),
-                                    GTK_RECENTLY_USED_FILE,
-                                    NULL);
-  
-  dispatch = g_slice_new (ThreadsDispatch);
-  dispatch->func = gtk_recent_manager_poll_timeout;
-  dispatch->data = manager;
-  dispatch->notify = NULL;
-  priv->poll_timeout = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT + 30,
-                                                   POLL_DELTA,
-                                                   threads_dispatch,
-                                                   dispatch,
-                                                   threads_free);
 
-  build_recent_items_list (manager);
+  /* this will take care of building the files list */
+  filename = g_build_filename (g_get_home_dir (),
+                               GTK_RECENTLY_USED_FILE,
+                               NULL);
+  gtk_recent_manager_set_filename (manager, filename);
+  g_free (filename);
 }
 
 static void
@@ -399,10 +346,13 @@ gtk_recent_manager_dispose (GObject *object)
   GtkRecentManager *manager = GTK_RECENT_MANAGER (object);
   GtkRecentManagerPrivate *priv = manager->priv;
 
-  if (priv->poll_timeout)
+  if (priv->monitor)
     {
-      g_source_remove (priv->poll_timeout);
-      priv->poll_timeout = 0;
+      g_signal_handlers_disconnect_by_func (priv->monitor,
+                                            G_CALLBACK (gtk_recent_manager_monitor_changed),
+                                            manager);
+      g_object_unref (priv->monitor);
+      priv->monitor = NULL;
     }
 
   G_OBJECT_CLASS (gtk_recent_manager_parent_class)->dispose (object);
@@ -432,15 +382,12 @@ gtk_recent_manager_real_changed (GtkRecentManager *manager)
   if (priv->is_dirty)
     {
       GError *write_error;
-      struct stat stat_buf;
       
       /* we are marked as dirty, so we dump the content of our
        * recently used items list
        */
       g_assert (priv->filename != NULL);
 
-      priv->write_in_progress = TRUE;
-
       /* if no container object has been defined, we create a new
        * empty container, and dump it
        */
@@ -461,26 +408,6 @@ gtk_recent_manager_real_changed (GtkRecentManager *manager)
          g_error_free (write_error);
        }
 
-      priv->write_in_progress = FALSE;
-         
-      /* we have sync'ed our list with the storage file, so we
-       * update the file mtime in order to skip the timed check
-       * and spare us from a re-read.
-       */
-      if (g_stat (priv->filename, &stat_buf) < 0)
-       {
-          filename_warning ("Unable to stat() the recently used resources file "
-                           "at `%s': %s.",
-                           priv->filename,
-                           g_strerror (errno));
-
-         g_object_thaw_notify (G_OBJECT (manager));
-
-         return;
-       }
-      
-      priv->last_mtime = stat_buf.st_mtime;
-      
       /* mark us as clean */
       priv->is_dirty = FALSE;
     }
@@ -496,44 +423,28 @@ gtk_recent_manager_real_changed (GtkRecentManager *manager)
   g_object_thaw_notify (G_OBJECT (manager));
 }
 
-/* timed poll()-ing of the recently used resources file.
- * an event-based system would be more efficient.
- */
-static gboolean
-gtk_recent_manager_poll_timeout (gpointer data)
+static void
+gtk_recent_manager_monitor_changed (GFileMonitor      *monitor,
+                                    GFile             *file,
+                                    GFile             *other_file,
+                                    GFileMonitorEvent  event_type,
+                                    gpointer           user_data)
 {
-  GtkRecentManager *manager = GTK_RECENT_MANAGER (data);
-  GtkRecentManagerPrivate *priv = manager->priv;
-  struct stat stat_buf;
-  int stat_res;
-
-  /* wait for the next timeout if we have a read/write in progress */
-  if (priv->write_in_progress || priv->read_in_progress)
-    return TRUE;
+  GtkRecentManager *manager = user_data;
 
-  stat_res = g_stat (priv->filename, &stat_buf);
-  if (stat_res < 0)
+  switch (event_type)
     {
-      /* the file does not exist, yet, so we wait */
-      if (errno == ENOENT)
-        return TRUE;
-      
-      filename_warning ("Unable to stat() the recently used resources file "
-                       "at `%s': %s.",
-                       priv->filename,
-                       g_strerror (errno));
-      
-      return TRUE;
-    }
-
-  /* the file didn't change from the last poll, so we bail out */
-  if (stat_buf.st_mtime == priv->last_mtime)
-    return TRUE;
+    case G_FILE_MONITOR_EVENT_CHANGED:
+    case G_FILE_MONITOR_EVENT_CREATED:
+      gtk_recent_manager_changed (manager);
+      break;
 
-  /* the file has been changed, hence we emit the "changed" signal */
-  gtk_recent_manager_changed (manager);
+    case G_FILE_MONITOR_EVENT_DELETED:
+      break;
 
-  return TRUE;
+    default:
+      break;
+    }
 }
 
 static void
@@ -541,37 +452,48 @@ gtk_recent_manager_set_filename (GtkRecentManager *manager,
                                 const gchar      *filename)
 {
   GtkRecentManagerPrivate *priv;
-  ThreadsDispatch *dispatch;
+  GFile *file;
+  GError *error;
   
   g_assert (GTK_IS_RECENT_MANAGER (manager));
+
   priv = manager->priv;
   
+  g_free (priv->filename);
+
+  if (priv->monitor)
+    {
+      g_signal_handlers_disconnect_by_func (priv->monitor,
+                                            G_CALLBACK (gtk_recent_manager_monitor_changed),
+                                            manager);
+      g_object_unref (priv->monitor);
+      priv->monitor = NULL;
+    }
+
   if (!filename || filename[0] == '\0')
     return;
   
-  g_free (manager->priv->filename);
+  priv->filename = g_strdup (filename);
+  file = g_file_new_for_path (priv->filename);
 
-  if (manager->priv->poll_timeout)
+  error = NULL;
+  priv->monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error);
+  if (error)
     {
-      g_source_remove (manager->priv->poll_timeout);
-      manager->priv->poll_timeout = 0;
+      filename_warning ("Unable to monitor `%s': %s\n"
+                        "The GtkRecentManager will not update its contents "
+                        "if the file is changed from other instances",
+                        priv->filename,
+                        error->message);
+      g_error_free (error);
     }
+  else
+    g_signal_connect (priv->monitor, "changed",
+                      G_CALLBACK (gtk_recent_manager_monitor_changed),
+                      manager);
 
-  priv->filename = g_strdup (filename);
+  g_object_unref (file);
 
-  dispatch = g_slice_new (ThreadsDispatch);
-  dispatch->func = gtk_recent_manager_poll_timeout;
-  dispatch->data = manager;
-  dispatch->notify = NULL;
-  priv->poll_timeout = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT + 30,
-                                                   POLL_DELTA,
-                                                   threads_dispatch,
-                                                   dispatch,
-                                                   threads_free);
-
-  /* mark us clean, so that we can re-read the list
-   * of recently used resources
-   */
   priv->is_dirty = FALSE;
   build_recent_items_list (manager);
 }
@@ -584,14 +506,10 @@ gtk_recent_manager_set_filename (GtkRecentManager *manager,
 static void
 build_recent_items_list (GtkRecentManager *manager)
 {
-  GtkRecentManagerPrivate *priv;
-  struct stat stat_buf;
-  int stat_res;
-  gboolean res;
+  GtkRecentManagerPrivate *priv = manager->priv;
   GError *read_error;
   gint size;
 
-  priv = manager->priv;
   g_assert (priv->filename != NULL);
   
   if (!priv->recent_items)
@@ -600,60 +518,41 @@ build_recent_items_list (GtkRecentManager *manager)
       priv->size = 0;
     }
 
-  stat_res = g_stat (priv->filename, &stat_buf);
-  if (stat_res < 0)
-    {
-      /* the file doesn't exists, so we bail out and wait for the first
-       * write operation
-       */
-
-      if (errno == ENOENT)
-        return;
-      else
-        {
-          filename_warning ("Attempting to read the recently used resources file "
-                           "at `%s', but an error occurred: %s. Aborting.",
-                           priv->filename,
-                           g_strerror (errno));
-
-          return;
-        }
-    }
-
-  /* record the last mtime, for later use */
-  priv->last_mtime = stat_buf.st_mtime;
-  
-  priv->read_in_progress = TRUE;
-
   /* the file exists, and it's valid (we hope); if not, destroy the container
    * object and hope for a better result when the next "changed" signal is
    * fired. */
   read_error = NULL;
-  res = g_bookmark_file_load_from_file (priv->recent_items,
-                                       priv->filename,
-                                       &read_error);
+  g_bookmark_file_load_from_file (priv->recent_items, priv->filename, &read_error);
   if (read_error)
     {
-      filename_warning ("Attempting to read the recently used resources file "
-                       "at `%s', but the parser failed: %s.",
-                       priv->filename,
-                       read_error->message);
+      /* if the file does not exist we just wait for the first write
+       * operation on this recent manager instance, to avoid creating
+       * empty files and leading to spurious file system events (Sabayon
+       * will not be happy about those)
+       */
+      if (read_error->domain == G_FILE_ERROR &&
+          read_error->code != G_FILE_ERROR_NOENT)
+        filename_warning ("Attempting to read the recently used resources "
+                          "file at `%s', but the parser failed: %s.",
+                          priv->filename,
+                          read_error->message);
 
       g_bookmark_file_free (priv->recent_items);
       priv->recent_items = NULL;
 
       g_error_free (read_error);
     }
-
-  size = g_bookmark_file_get_size (priv->recent_items);
-  if (priv->size != size)
+  else
     {
-      priv->size = size;
-      
-      g_object_notify (G_OBJECT (manager), "size");
+      size = g_bookmark_file_get_size (priv->recent_items);
+      if (priv->size != size)
+        {
+          priv->size = size;
+
+          g_object_notify (G_OBJECT (manager), "size");
+        }
     }
 
-  priv->read_in_progress = FALSE;
   priv->is_dirty = FALSE;
 }