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
This commit is contained in:
Colin Walters
2015-02-12 18:20:14 -05:00
parent 9d6d30475b
commit 52cd62d946
5 changed files with 218 additions and 36 deletions

View File

@@ -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

View File

@@ -573,6 +573,121 @@ g_file_enumerator_set_pending (GFileEnumerator *enumerator,
enumerator->priv->pending = pending;
}
/**
* 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);
}

View File

@@ -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__ */

View File

@@ -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_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
cleanup_dir_recurse (descend, root);
error = NULL;
res = g_file_delete (descend, NULL, &error);
g_assert_cmpint (res, ==, TRUE);
g_object_unref (descend);
error = NULL;
info = g_file_enumerator_next_file (enumerator, NULL, &error);
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
{
g_clear_error (&local_error);
ret = TRUE;
}
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);
while (TRUE)
{
GFile *child;
GFileInfo *finfo;
char *relative_path;
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);
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;
}
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);
}

View File

@@ -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);