[glib] filenumerator: Add g_file_enumerator_iterate()



commit 52cd62d946946af3b0c9dac644ddc4bd517d131b
Author: Colin Walters <walters verbum org>
Date:   Thu Feb 12 18:20:14 2015 -0500

    filenumerator: Add g_file_enumerator_iterate()
    
    This is *significantly* more pleasant to use from C (while handling
    errors and memory cleanup).
    
    While we're here, change some ugly, leaky code in
    tests/desktop-app-info.c to use it, in addition to a test case
    in tests/file.c.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=661554

 docs/reference/gio/gio-sections.txt |    1 +
 gio/gfileenumerator.c               |  116 +++++++++++++++++++++++++++++++++++
 gio/gfileenumerator.h               |    8 +++
 gio/tests/desktop-app-info.c        |   77 +++++++++++++----------
 gio/tests/file.c                    |   46 ++++++++++++++
 5 files changed, 215 insertions(+), 33 deletions(-)
---
diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt
index 5b691a4..757ccf2 100644
--- a/docs/reference/gio/gio-sections.txt
+++ b/docs/reference/gio/gio-sections.txt
@@ -220,6 +220,7 @@ g_file_get_type
 <FILE>gfileenumerator</FILE>
 <TITLE>GFileEnumerator</TITLE>
 GFileEnumerator
+g_file_enumerator_iterate
 g_file_enumerator_next_file
 g_file_enumerator_close
 g_file_enumerator_next_files_async
diff --git a/gio/gfileenumerator.c b/gio/gfileenumerator.c
index c0d4054..73014f2 100644
--- a/gio/gfileenumerator.c
+++ b/gio/gfileenumerator.c
@@ -574,6 +574,121 @@ g_file_enumerator_set_pending (GFileEnumerator *enumerator,
 }
 
 /**
+ * g_file_enumerator_iterate:
+ * @direnum: an open #GFileEnumerator
+ * @out_info: (out) (transfer none) (allow-none): Output location for the next #GFileInfo, or %NULL
+ * @out_child: (out) (transfer none) (allow-none): Output location for the next #GFile, or %NULL
+ * @cancellable: a #GCancellable
+ * @error: a #GError
+ *
+ * This is a version of g_file_enumerator_next_file() that's easier to
+ * use correctly from C programs.  With g_file_enumerator_next_file(),
+ * the gboolean return value signifies "end of iteration or error", which
+ * requires allocation of a temporary #GError.
+ *
+ * In contrast, with this function, a %FALSE return from
+ * gs_file_enumerator_iterate() <emphasis>always</emphasis> means
+ * "error".  End of iteration is signaled by @out_info or @out_child being %NULL.
+ *
+ * Another crucial difference is that the references for @out_info and
+ * @out_child are owned by @direnum (they are cached as hidden
+ * properties).  You must not unref them in your own code.  This makes
+ * memory management significantly easier for C code in combination
+ * with loops.
+ *
+ * Finally, this function optionally allows retrieving a #GFile as
+ * well.
+ *
+ * You must specify at least one of @out_info or @out_child.
+ *
+ * The code pattern for correctly using g_file_enumerator_iterate() from C
+ * is:
+ *
+ * |[
+ * direnum = g_file_enumerate_children (file, ...);
+ * while (TRUE)
+ *   {
+ *     GFileInfo *info;
+ *     if (!g_file_enumerator_iterate (direnum, &info, NULL, cancellable, error))
+ *       goto out;
+ *     if (!info)
+ *       break;
+ *     ... do stuff with "info"; do not unref it! ...
+ *   }
+ * 
+ * out:
+ *   g_object_unref (direnum); // Note: frees the last @info
+ * ]|
+ *
+ *
+ * Since: 2.44
+ */
+gboolean
+g_file_enumerator_iterate (GFileEnumerator  *direnum,
+                           GFileInfo       **out_info,
+                           GFile           **out_child,
+                           GCancellable     *cancellable,
+                           GError          **error)
+{
+  gboolean ret = FALSE;
+  GError *temp_error = NULL;
+  GFileInfo *ret_info = NULL;
+
+  static GQuark cached_info_quark;
+  static GQuark cached_child_quark;
+  static gsize quarks_initialized;
+
+  g_return_val_if_fail (direnum != NULL, FALSE);
+  g_return_val_if_fail (out_info != NULL || out_child != NULL, FALSE);
+
+  if (g_once_init_enter (&quarks_initialized))
+    {
+      cached_info_quark = g_quark_from_static_string ("g-cached-info");
+      cached_child_quark = g_quark_from_static_string ("g-cached-child");
+      g_once_init_leave (&quarks_initialized, 1);
+    }
+
+  ret_info = g_file_enumerator_next_file (direnum, cancellable, &temp_error);
+  if (temp_error != NULL)
+    {
+      g_propagate_error (error, temp_error);
+      goto out;
+    }
+
+  if (ret_info)
+    { 
+      if (out_info != NULL)
+        {
+          g_object_set_qdata_full ((GObject*)direnum, cached_info_quark, ret_info, 
(GDestroyNotify)g_object_unref);
+          *out_info = ret_info;
+        }
+      if (out_child != NULL)
+        {
+          const char *name = g_file_info_get_name (ret_info);
+
+          if (G_UNLIKELY (name == NULL))
+            g_warning ("g_file_enumerator_iterate() created without standard::name");
+          else
+            {
+              *out_child = g_file_get_child (g_file_enumerator_get_container (direnum), name);
+              g_object_set_qdata_full ((GObject*)direnum, cached_child_quark, *out_child, 
(GDestroyNotify)g_object_unref);
+            }
+        }
+    }
+  else
+    {
+      if (out_info)
+        *out_info = NULL;
+      if (out_child)
+        *out_child = NULL;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
  * g_file_enumerator_get_container:
  * @enumerator: a #GFileEnumerator
  *
@@ -747,3 +862,4 @@ g_file_enumerator_real_close_finish (GFileEnumerator  *enumerator,
 
   return g_task_propagate_boolean (G_TASK (result), error);
 }
+
diff --git a/gio/gfileenumerator.h b/gio/gfileenumerator.h
index ceb62a5..fddcbe1 100644
--- a/gio/gfileenumerator.h
+++ b/gio/gfileenumerator.h
@@ -139,6 +139,14 @@ GLIB_AVAILABLE_IN_2_36
 GFile *    g_file_enumerator_get_child         (GFileEnumerator *enumerator,
                                                 GFileInfo       *info);
 
+GLIB_AVAILABLE_IN_2_44
+gboolean   g_file_enumerator_iterate           (GFileEnumerator  *direnum,
+                                                GFileInfo       **out_info,
+                                                GFile           **out_child,
+                                                GCancellable     *cancellable,
+                                                GError          **error);
+
+
 G_END_DECLS
 
 #endif /* __G_FILE_ENUMERATOR_H__ */
diff --git a/gio/tests/desktop-app-info.c b/gio/tests/desktop-app-info.c
index ccd6357..31ea215 100644
--- a/gio/tests/desktop-app-info.c
+++ b/gio/tests/desktop-app-info.c
@@ -268,64 +268,75 @@ test_last_used (void)
   g_object_unref (default_app);
 }
 
-static void
-cleanup_dir_recurse (GFile *parent, GFile *root)
+static gboolean
+cleanup_dir_recurse (GFile   *parent,
+                     GFile   *root,
+                     GError **error)
 {
-  gboolean res;
-  GError *error;
+  gboolean ret = FALSE;
   GFileEnumerator *enumerator;
-  GFileInfo *info;
-  GFile *descend;
-  char *relative_path;
+  GError *local_error = NULL;
 
   g_assert (root != NULL);
 
-  error = NULL;
   enumerator =
     g_file_enumerate_children (parent, "*",
                                G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL,
-                               &error);
-  if (! enumerator)
-          return;
-  error = NULL;
-  info = g_file_enumerator_next_file (enumerator, NULL, &error);
-  while ((info) && (!error))
+                               &local_error);
+  if (!enumerator)
     {
-      descend = g_file_get_child (parent, g_file_info_get_name (info));
-      g_assert (descend != NULL);
-      relative_path = g_file_get_relative_path (root, descend);
-      g_assert (relative_path != NULL);
+      if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_clear_error (&local_error);
+          ret = TRUE;
+        }
+      goto out;
+    }
 
-      if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
-          cleanup_dir_recurse (descend, root);
+  while (TRUE)
+    {
+      GFile *child;
+      GFileInfo *finfo;
+      char *relative_path;
 
-      error = NULL;
-      res = g_file_delete (descend, NULL, &error);
-      g_assert_cmpint (res, ==, TRUE);
+      if (!g_file_enumerator_iterate (enumerator, &finfo, &child, NULL, error))
+        goto out;
+      if (!finfo)
+        break;
+        
+      relative_path = g_file_get_relative_path (root, child);
+      g_assert (relative_path != NULL);
+      g_free (relative_path);
 
-      g_object_unref (descend);
-      error = NULL;
-      info = g_file_enumerator_next_file (enumerator, NULL, &error);
+      if (g_file_info_get_file_type (finfo) == G_FILE_TYPE_DIRECTORY)
+        {
+          if (!cleanup_dir_recurse (child, root, error))
+            goto out;
+        }
+
+      if (!g_file_delete (child, NULL, error))
+        goto out;
     }
-  g_assert (error == NULL);
 
-  error = NULL;
-  res = g_file_enumerator_close (enumerator, NULL, &error);
-  g_assert_cmpint (res, ==, TRUE);
-  g_assert (error == NULL);
+  ret = TRUE;
+ out:
+  return ret;
 }
 
 static void
 cleanup_subdirs (const char *base_dir)
 {
   GFile *base, *file;
+  GError *error = NULL;
  
   base = g_file_new_for_path (base_dir);  
   file = g_file_get_child (base, "applications");
-  cleanup_dir_recurse (file, file);
+  (void) cleanup_dir_recurse (file, file, &error);
+  g_assert_no_error (error);
   g_object_unref (file);
   file = g_file_get_child (base, "mime");
-  cleanup_dir_recurse (file, file);
+  (void) cleanup_dir_recurse (file, file, &error);
+  g_assert_no_error (error);
   g_object_unref (file);
 }
 
diff --git a/gio/tests/file.c b/gio/tests/file.c
index cab1835..cba82d1 100644
--- a/gio/tests/file.c
+++ b/gio/tests/file.c
@@ -646,6 +646,7 @@ test_replace_cancel (void)
   GCancellable *cancellable;
   gchar *path;
   gsize nwrote;
+  guint count;
   GError *error = NULL;
 
   g_test_bug ("629301");
@@ -691,6 +692,51 @@ test_replace_cancel (void)
   g_assert_no_error (error);
   g_object_unref (fenum);
 
+  /* Also test the g_file_enumerator_iterate() API */
+  fenum = g_file_enumerate_children (tmpdir, NULL, 0, NULL, &error);
+  g_assert_no_error (error);
+  count = 0;
+
+  while (TRUE)
+    {
+      gboolean ret = g_file_enumerator_iterate (fenum, &info, NULL, NULL, &error);
+      g_assert (ret);
+      g_assert_no_error (error);
+      if (!info)
+        break;
+      count++;
+    }
+  g_assert_cmpint (count, ==, 2);
+
+  g_file_enumerator_close (fenum, NULL, &error);
+  g_assert_no_error (error);
+  g_object_unref (fenum);
+
+  /* Now test just getting child from the g_file_enumerator_iterate() API */
+  fenum = g_file_enumerate_children (tmpdir, "standard::name", 0, NULL, &error);
+  g_assert_no_error (error);
+  count = 0;
+
+  while (TRUE)
+    {
+      GFile *child;
+      gboolean ret = g_file_enumerator_iterate (fenum, NULL, &child, NULL, &error);
+
+      g_assert (ret);
+      g_assert_no_error (error);
+
+      if (!child)
+        break;
+
+      g_assert (G_IS_FILE (child));
+      count++;
+    }
+  g_assert_cmpint (count, ==, 2);
+
+  g_file_enumerator_close (fenum, NULL, &error);
+  g_assert_no_error (error);
+  g_object_unref (fenum);
+
   /* Make sure the temporary gets deleted even if we cancel. */
   cancellable = g_cancellable_new ();
   g_cancellable_cancel (cancellable);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]