mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2025-08-13 20:47:46 +02:00
GFile: Add g_file_peek_path()
This is a variant of g_file_get_path() which returns a const string to the caller, rather than transferring ownership. I've been carrying `gs_file_get_path_cached()` in libgsystem and it has seen a lot of use in the ostree and flatpak codebases. There are probably others too. I think language bindings like Python/Gjs could also use this to avoid an extra malloc (i.e. we could transparently replace `g_file_get_path()` with `g_file_peek_path()`. (Originally by Colin Walters. Tweaked by Philip Withnall to update to 2.56, change the function name and drop the locking.) https://bugzilla.gnome.org/show_bug.cgi?id=767976
This commit is contained in:
committed by
Philip Withnall
parent
261cb8ea86
commit
4808a957b5
@@ -91,6 +91,7 @@ g_file_hash
|
|||||||
g_file_equal
|
g_file_equal
|
||||||
g_file_get_basename
|
g_file_get_basename
|
||||||
g_file_get_path
|
g_file_get_path
|
||||||
|
g_file_peek_path
|
||||||
g_file_get_uri
|
g_file_get_uri
|
||||||
g_file_get_parse_name
|
g_file_get_parse_name
|
||||||
g_file_get_parent
|
g_file_get_parent
|
||||||
|
101
gio/gfile.c
101
gio/gfile.c
@@ -534,6 +534,107 @@ g_file_get_path (GFile *file)
|
|||||||
return (* iface->get_path) (file);
|
return (* iface->get_path) (file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Original commit introducing this in libgsystem:
|
||||||
|
*
|
||||||
|
* fileutil: Handle recent: and trash: URIs
|
||||||
|
*
|
||||||
|
* The gs_file_get_path_cached() was rather brittle in its handling
|
||||||
|
* of URIs. It would assert() when a GFile didn't have a backing path
|
||||||
|
* (such as when handling trash: or recent: URIs), and didn't know
|
||||||
|
* how to get the target URI for those items either.
|
||||||
|
*
|
||||||
|
* Make sure that we do not assert() when a backing path cannot be
|
||||||
|
* found, and handle recent: and trash: URIs.
|
||||||
|
*
|
||||||
|
* https://bugzilla.gnome.org/show_bug.cgi?id=708435
|
||||||
|
*/
|
||||||
|
static char *
|
||||||
|
file_get_target_path (GFile *file)
|
||||||
|
{
|
||||||
|
GFileInfo *info;
|
||||||
|
const char *target;
|
||||||
|
char *path;
|
||||||
|
|
||||||
|
info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
||||||
|
if (info == NULL)
|
||||||
|
return NULL;
|
||||||
|
target = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
|
||||||
|
path = g_filename_from_uri (target, NULL, NULL);
|
||||||
|
g_object_unref (info);
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *
|
||||||
|
file_peek_path_generic (GFile *file)
|
||||||
|
{
|
||||||
|
const char *path;
|
||||||
|
static GQuark _file_path_quark = 0;
|
||||||
|
|
||||||
|
if (G_UNLIKELY (_file_path_quark) == 0)
|
||||||
|
_file_path_quark = g_quark_from_static_string ("gio-file-path");
|
||||||
|
|
||||||
|
/* We need to be careful about threading, as two threads calling
|
||||||
|
* g_file_peek_path() on the same file could race: both would see
|
||||||
|
* (g_object_get_qdata(…) == NULL) to begin with, both would generate and add
|
||||||
|
* the path, but the second thread to add it would end up freeing the path
|
||||||
|
* set by the first thread. The first thread would still return the pointer
|
||||||
|
* to that freed path, though, resulting an a read-after-free. Handle that
|
||||||
|
* with a compare-and-swap loop. The g_object_*_qdata() functions are atomic. */
|
||||||
|
|
||||||
|
while (TRUE)
|
||||||
|
{
|
||||||
|
gchar *new_path = NULL;
|
||||||
|
|
||||||
|
path = g_object_get_qdata ((GObject*)file, _file_path_quark);
|
||||||
|
|
||||||
|
if (path != NULL)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (g_file_has_uri_scheme (file, "trash") ||
|
||||||
|
g_file_has_uri_scheme (file, "recent"))
|
||||||
|
new_path = file_get_target_path (file);
|
||||||
|
else
|
||||||
|
new_path = g_file_get_path (file);
|
||||||
|
if (new_path == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* By passing NULL here, we ensure we never replace existing data: */
|
||||||
|
if (g_object_replace_qdata ((GObject *) file, _file_path_quark,
|
||||||
|
NULL, (gpointer) new_path,
|
||||||
|
(GDestroyNotify) g_free, NULL))
|
||||||
|
break;
|
||||||
|
else
|
||||||
|
g_free (new_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* g_file_peek_path:
|
||||||
|
* @file: input #GFile
|
||||||
|
*
|
||||||
|
* Exactly like g_file_get_path(), but caches the result via
|
||||||
|
* g_object_set_qdata_full(). This is useful for example in C
|
||||||
|
* applications which mix `g_file_*` APIs with native ones. It
|
||||||
|
* also avoids an extra duplicated string when possible, so will be
|
||||||
|
* generally more efficient.
|
||||||
|
*
|
||||||
|
* This call does no blocking I/O.
|
||||||
|
*
|
||||||
|
* Returns: (type filename) (nullable): string containing the #GFile's path,
|
||||||
|
* or %NULL if no such path exists. The returned string is owned by @file.
|
||||||
|
* Since: 2.56
|
||||||
|
*/
|
||||||
|
const char *
|
||||||
|
g_file_peek_path (GFile *file)
|
||||||
|
{
|
||||||
|
if (G_IS_LOCAL_FILE (file))
|
||||||
|
return _g_local_file_get_filename ((GLocalFile *) file);
|
||||||
|
return file_peek_path_generic (file);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* g_file_get_uri:
|
* g_file_get_uri:
|
||||||
* @file: input #GFile
|
* @file: input #GFile
|
||||||
|
@@ -620,6 +620,8 @@ GLIB_AVAILABLE_IN_ALL
|
|||||||
char * g_file_get_basename (GFile *file);
|
char * g_file_get_basename (GFile *file);
|
||||||
GLIB_AVAILABLE_IN_ALL
|
GLIB_AVAILABLE_IN_ALL
|
||||||
char * g_file_get_path (GFile *file);
|
char * g_file_get_path (GFile *file);
|
||||||
|
GLIB_AVAILABLE_IN_2_56
|
||||||
|
const char * g_file_peek_path (GFile *file);
|
||||||
GLIB_AVAILABLE_IN_ALL
|
GLIB_AVAILABLE_IN_ALL
|
||||||
char * g_file_get_uri (GFile *file);
|
char * g_file_get_uri (GFile *file);
|
||||||
GLIB_AVAILABLE_IN_ALL
|
GLIB_AVAILABLE_IN_ALL
|
||||||
|
@@ -169,9 +169,12 @@ monitor_changed (GFileMonitor *monitor,
|
|||||||
{
|
{
|
||||||
CreateDeleteData *data = user_data;
|
CreateDeleteData *data = user_data;
|
||||||
gchar *path;
|
gchar *path;
|
||||||
|
const gchar *peeked_path;
|
||||||
|
|
||||||
path = g_file_get_path (file);
|
path = g_file_get_path (file);
|
||||||
|
peeked_path = g_file_peek_path (file);
|
||||||
g_assert_cmpstr (data->monitor_path, ==, path);
|
g_assert_cmpstr (data->monitor_path, ==, path);
|
||||||
|
g_assert_cmpstr (path, ==, peeked_path);
|
||||||
g_free (path);
|
g_free (path);
|
||||||
|
|
||||||
if (event_type == G_FILE_MONITOR_EVENT_CREATED)
|
if (event_type == G_FILE_MONITOR_EVENT_CREATED)
|
||||||
@@ -619,7 +622,7 @@ static void
|
|||||||
test_replace_load (void)
|
test_replace_load (void)
|
||||||
{
|
{
|
||||||
ReplaceLoadData *data;
|
ReplaceLoadData *data;
|
||||||
gchar *path;
|
const gchar *path;
|
||||||
GFileIOStream *iostream;
|
GFileIOStream *iostream;
|
||||||
|
|
||||||
data = g_new0 (ReplaceLoadData, 1);
|
data = g_new0 (ReplaceLoadData, 1);
|
||||||
@@ -631,7 +634,7 @@ test_replace_load (void)
|
|||||||
g_assert (data->file != NULL);
|
g_assert (data->file != NULL);
|
||||||
g_object_unref (iostream);
|
g_object_unref (iostream);
|
||||||
|
|
||||||
path = g_file_get_path (data->file);
|
path = g_file_peek_path (data->file);
|
||||||
remove (path);
|
remove (path);
|
||||||
|
|
||||||
g_assert (!g_file_query_exists (data->file, NULL));
|
g_assert (!g_file_query_exists (data->file, NULL));
|
||||||
@@ -653,7 +656,6 @@ test_replace_load (void)
|
|||||||
g_main_loop_unref (data->loop);
|
g_main_loop_unref (data->loop);
|
||||||
g_object_unref (data->file);
|
g_object_unref (data->file);
|
||||||
g_free (data);
|
g_free (data);
|
||||||
free (path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
Reference in New Issue
Block a user