Search code examples
glibgiogvfs

Testing for GVfs metadata support in C


I am trying to add support for per-directory viewing settings to the Thunar file browser of the Xfce desktop. So for example if a user chooses to view the contents of a directory as a list rather than as a grid of icons, this setting is remembered for that directory and will be used whenever that directory is viewed.

Now Thunar is built on GLib, and the mechanism we have chosen to use to implement this is to store metadata using GFile attributes, using methods like g_file_set_attributes_async to store keys with names such as "metadata::thunar-view-type". The per-directory feature can be turned on or off by the user via a checkbox in a preferences dialog. My knowledge of GIO and GLib is pretty limited, but I have now managed to get this all working as desired (you can see my merge request here if you are interested).

Now as I understand it, the functionality that I am using here relies on something called "GVfs metadata", and as I understand it this might not be available on all systems. On systems where GVfs metadata is not available, I want to turn this functionality off and in particular make the checkbox in the preferences dialog insensitive (i.e. greyed out). Thus I need to write a function to detect if gvfs metadata support is available, by which I mean whether I can use functions like g_file_set_attributes_async to successfully save metadata so that it will be available in future.

Thunar is written in C, so this function needs to be written in C using the C API for GLib, GIO, etc. The function I have come up with (from much reading of API documentation, modifying code scraps I have found, and experimentation) is as follows.

gboolean
thunar_g_vfs_metadata_is_supported  (void)
{
  GDBusMessage     *send, *reply;
  GDBusConnection  *conn;
  GVariant         *v1, *v2;
  GError           *error = NULL;
  const gchar     **service_names;
  gboolean          metadata_found;

  /* connect to the session bus */
  conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);

  /* check that the connection was opened sucessfully */
  if (error != NULL)
    {
      g_error_free (error);
      return FALSE;
    }

  /* create the message to send to list the available services */
  send = g_dbus_message_new_method_call ("org.freedesktop.DBus",
                                         "/org/freedesktop/DBus",
                                         "org.freedesktop.DBus",
                                         "ListNames");

  /* send the message and wait for the reply */
  reply = g_dbus_connection_send_message_with_reply_sync (conn, send, G_DBUS_SEND_MESSAGE_FLAGS_NONE,
                                                          -1, NULL, NULL, &error);

  /* release the connection and the sent message */
  g_object_unref (send);
  g_object_unref (conn);

  /* check if we got a sucessful reply */
  if (error != NULL)
    {
      g_error_free (error);
      return FALSE;
    }

  /* extract the GVariant with the array of strings describing the available services */
  v1 = g_dbus_message_get_body (reply); /* v1 belongs to reply and must not be freed */
  if (v1 == NULL || !g_variant_is_container (v1) || g_variant_n_children (v1) < 1)
    {
      g_object_unref (reply);
      return FALSE;
    }
  v2 = g_variant_get_child_value (v1, 0);
  g_object_unref (reply);

  /* check that the GVariant we have been given does contain an array of strings */
  if (!g_variant_is_of_type (v2, G_VARIANT_TYPE_STRING_ARRAY))
    {
      g_variant_unref (v2);
      return FALSE;
    }

  /* search through the list of service names to see if gvfs metadata is present */
  metadata_found = FALSE;
  service_names = g_variant_get_strv (v2, NULL);
  for (int i=0; service_names[i] != NULL; i++)
    if (g_strcmp0 (service_names[i], "org.gtk.vfs.Metadata") == 0)
      metadata_found = TRUE;

  g_free (service_names);
  g_variant_unref (v2);

  return metadata_found;
}

As you can see, this function uses DBus to query service names to see if the necessary service is available. Now, as far as I have been able to test it, this function works as I want it to. However, during a code review it has been questioned whether this can be done without relying on DBus (which might itself not be available even though GVfs metadata is).

Thus (at last!) my question: what is the best (i.e. most robust and accurate) way to test for GVfs metadata support via the C API for GLib, GIO, etc?. As I said above, by "GVfs metadata support" I mean "can I use functions like g_file_set_attributes_async to successfully save metadata so that it will be available in future?".

One method I have considered is looking at the list of running processes for the name "gvfsd-metadata", but that seems a bit kludgy to me.

Also, as mentioned above I am very much a novice with these technologies, so I is absolutely possible that I have misunderstood stuff here, so if you spot any errors in the assertions I have made above, please let me know.

Thanks!

(And yes, usual story, I'm a long time reader of SO & co, but a first time asker, so please feel free to edit or let me know if I've done something wrong/bad)


Solution

  • Call g_file_query_settable_attributes() and g_file_query_writable_namespaces() on the GFile, as described in the GFileInfo documentation:

    However, not all attributes can be changed in the file. For instance, the actual size of a file cannot be changed via g_file_info_set_size(). You may call g_file_query_settable_attributes() and g_file_query_writable_namespaces() to discover the settable attributes of a particular file at runtime.