gio: Expose g_file_build_attribute_list_for_copy

Expose a function that prepares an attribute query string to be passed
to g_file_query_info() to get a list of attributes normally copied with
the file. This function is used by the implementation of
g_file_copy_attributes, and it's useful if one needs to split
g_file_copy_attributes into two stages, for example, when nautilus does
a recursive move of a directory. When files are moved from the source
directory, its modification time changes. To preserve the mtime on the
destination directory, it has to be queried before moving files and set
after doing it, hence these two stages.

Signed-off-by: Maxim Mikityanskiy <maxtram95@gmail.com>
This commit is contained in:
Maxim Mikityanskiy 2020-09-07 19:20:01 +03:00 committed by Philip Withnall
parent 5017de6567
commit 094eca7076
4 changed files with 149 additions and 45 deletions

View File

@ -199,6 +199,7 @@ g_file_replace_contents
g_file_replace_contents_async
g_file_replace_contents_bytes_async
g_file_replace_contents_finish
g_file_build_attribute_list_for_copy
g_file_copy_attributes
g_file_create_readwrite
g_file_create_readwrite_async

View File

@ -2695,14 +2695,36 @@ should_copy (GFileAttributeInfo *info,
return info->flags & G_FILE_ATTRIBUTE_INFO_COPY_WITH_FILE;
}
static gboolean
build_attribute_list_for_copy (GFile *file,
GFileCopyFlags flags,
char **out_attributes,
GCancellable *cancellable,
GError **error)
/**
* g_file_build_attribute_list_for_copy:
* @file: a #GFile to copy attributes to
* @flags: a set of #GFileCopyFlags
* @cancellable: (nullable): optional #GCancellable object,
* %NULL to ignore
* @error: a #GError, %NULL to ignore
*
* Prepares the file attribute query string for copying to @file.
*
* This function prepares an attribute query string to be
* passed to g_file_query_info() to get a list of attributes
* normally copied with the file (see g_file_copy_attributes()
* for the detailed description). This function is used by the
* implementation of g_file_copy_attributes() and is useful
* when one needs to query and set the attributes in two
* stages (e.g., for recursive move of a directory).
*
* Returns: an attribute query string for g_file_query_info(),
* or %NULL if an error occurs.
*
* Since: 2.68
*/
char *
g_file_build_attribute_list_for_copy (GFile *file,
GFileCopyFlags flags,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
char *ret = NULL;
GFileAttributeInfoList *attributes = NULL, *namespaces = NULL;
GString *s = NULL;
gboolean first;
@ -2710,6 +2732,10 @@ build_attribute_list_for_copy (GFile *file,
gboolean copy_all_attributes;
gboolean skip_perms;
g_return_val_if_fail (G_IS_FILE (file), NULL);
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
copy_all_attributes = flags & G_FILE_COPY_ALL_METADATA;
skip_perms = (flags & G_FILE_COPY_TARGET_DEFAULT_PERMS) != 0;
@ -2763,8 +2789,7 @@ build_attribute_list_for_copy (GFile *file,
}
}
ret = TRUE;
*out_attributes = g_string_free (s, FALSE);
ret = g_string_free (s, FALSE);
s = NULL;
out:
if (s)
@ -2810,8 +2835,9 @@ g_file_copy_attributes (GFile *source,
GFileInfo *info;
gboolean source_nofollow_symlinks;
if (!build_attribute_list_for_copy (destination, flags, &attrs_to_read,
cancellable, error))
attrs_to_read = g_file_build_attribute_list_for_copy (destination, flags,
cancellable, error);
if (!attrs_to_read)
return FALSE;
source_nofollow_symlinks = flags & G_FILE_COPY_NOFOLLOW_SYMLINKS;
@ -3157,6 +3183,7 @@ file_copy_fallback (GFile *source,
char *attrs_to_read;
gboolean do_set_attributes = FALSE;
GFileCreateFlags create_flags;
GError *tmp_error = NULL;
/* need to know the file type */
info = g_file_query_info (source,
@ -3198,47 +3225,43 @@ file_copy_fallback (GFile *source,
goto out;
in = G_INPUT_STREAM (file_in);
if (!build_attribute_list_for_copy (destination, flags, &attrs_to_read,
cancellable, error))
attrs_to_read = g_file_build_attribute_list_for_copy (destination, flags,
cancellable, error);
if (!attrs_to_read)
goto out;
if (attrs_to_read != NULL)
/* Ok, ditch the previous lightweight info (on Unix we just
* called lstat()); at this point we gather all the information
* we need about the source from the opened file descriptor.
*/
g_object_unref (info);
info = g_file_input_stream_query_info (file_in, attrs_to_read,
cancellable, &tmp_error);
if (!info)
{
GError *tmp_error = NULL;
/* Ok, ditch the previous lightweight info (on Unix we just
* called lstat()); at this point we gather all the information
* we need about the source from the opened file descriptor.
/* Not all gvfs backends implement query_info_on_read(), we
* can just fall back to the pathname again.
* https://bugzilla.gnome.org/706254
*/
g_object_unref (info);
info = g_file_input_stream_query_info (file_in, attrs_to_read,
cancellable, &tmp_error);
if (!info)
if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
{
/* Not all gvfs backends implement query_info_on_read(), we
* can just fall back to the pathname again.
* https://bugzilla.gnome.org/706254
*/
if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
{
g_clear_error (&tmp_error);
info = g_file_query_info (source, attrs_to_read, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable, error);
}
else
{
g_free (attrs_to_read);
g_propagate_error (error, tmp_error);
goto out;
}
g_clear_error (&tmp_error);
info = g_file_query_info (source, attrs_to_read, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable, error);
}
else
{
g_free (attrs_to_read);
g_propagate_error (error, tmp_error);
goto out;
}
g_free (attrs_to_read);
if (!info)
goto out;
do_set_attributes = TRUE;
}
g_free (attrs_to_read);
if (!info)
goto out;
do_set_attributes = TRUE;
/* In the local file path, we pass down the source info which
* includes things like unix::mode, to ensure that the target file

View File

@ -1094,6 +1094,12 @@ gboolean g_file_eject_mountable_with_operation_finish (GFile
GAsyncResult *result,
GError **error);
GLIB_AVAILABLE_IN_2_68
char * g_file_build_attribute_list_for_copy (GFile *file,
GFileCopyFlags flags,
GCancellable *cancellable,
GError **error);
GLIB_AVAILABLE_IN_ALL
gboolean g_file_copy_attributes (GFile *source,
GFile *destination,

View File

@ -1780,6 +1780,79 @@ test_writev_async_all_too_big_vectors (void)
g_object_unref (file);
}
static void
test_build_attribute_list_for_copy (void)
{
GFile *tmpfile;
GFileIOStream *iostream;
GError *error = NULL;
const GFileCopyFlags test_flags[] =
{
G_FILE_COPY_NONE,
G_FILE_COPY_TARGET_DEFAULT_PERMS,
G_FILE_COPY_ALL_METADATA,
G_FILE_COPY_ALL_METADATA | G_FILE_COPY_TARGET_DEFAULT_PERMS,
};
gsize i;
char *attrs;
gchar *attrs_with_commas;
tmpfile = g_file_new_tmp ("tmp-build-attribute-list-for-copyXXXXXX",
&iostream, &error);
g_assert_no_error (error);
g_io_stream_close ((GIOStream*)iostream, NULL, &error);
g_assert_no_error (error);
g_clear_object (&iostream);
for (i = 0; i < G_N_ELEMENTS (test_flags); i++)
{
GFileCopyFlags flags = test_flags[i];
attrs = g_file_build_attribute_list_for_copy (tmpfile, flags, NULL, &error);
g_test_message ("Attributes for copy: %s", attrs);
g_assert_no_error (error);
g_assert_nonnull (attrs);
attrs_with_commas = g_strconcat (",", attrs, ",", NULL);
g_free (attrs);
/* See g_local_file_class_init for reference. */
if (flags & G_FILE_COPY_TARGET_DEFAULT_PERMS)
g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_MODE ","));
else
g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_MODE ","));
#ifdef G_OS_UNIX
if (flags & G_FILE_COPY_ALL_METADATA)
{
g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_UID ","));
g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_GID ","));
}
else
{
g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_UID ","));
g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_UNIX_GID ","));
}
#endif
#ifdef HAVE_UTIMES
g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_MODIFIED ","));
g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC ","));
if (flags & G_FILE_COPY_ALL_METADATA)
{
g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS ","));
g_assert_nonnull (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS_USEC ","));
}
else
{
g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS ","));
g_assert_null (g_strstr_len (attrs_with_commas, -1, "," G_FILE_ATTRIBUTE_TIME_ACCESS_USEC ","));
}
#endif
g_free (attrs_with_commas);
}
(void) g_file_delete (tmpfile, NULL, NULL);
g_clear_object (&tmpfile);
}
int
main (int argc, char *argv[])
{
@ -1817,6 +1890,7 @@ main (int argc, char *argv[])
g_test_add_func ("/file/writev/async_all-no-vectors", test_writev_async_all_no_vectors);
g_test_add_func ("/file/writev/async_all-to-big-vectors", test_writev_async_all_too_big_vectors);
g_test_add_func ("/file/writev/async_all-cancellation", test_writev_async_all_cancellation);
g_test_add_func ("/file/build-attribute-list-for-copy", test_build_attribute_list_for_copy);
return g_test_run ();
}