GIO Migration Guide

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license can be obtained by visiting this link or by writing to: Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

Revision History
Revision 1.04. December 2009ck
Initial version

Abstract

The purpose of this document is to provide a guide to migrate code that is currently using GnomeVFS to the new virtual filesystem layer (called GIO) that was introduced in Glib with the version 2.16 (GNOME 2.22)


Table of Contents

Introduction
Design differences
GIO/GVFS Design
Types and Objects
File and Directory Operations
Remote locations and mounting
Accessing and Modifying File Content
File Information
Navigating the Filesystem-Tree
File Transfers
Trash Handling
Streaming API
Asynchronous operations
Monitoring Files and Directories
Mounts, Volumes and Drives

Introduction

GnomeVFS, the GNOME Virtual Filesystem (GnomeVFS), was developed in 1999 by Ezeal Inc. as the filesystem abstraction layer for Nautilus, the GNOME filemanager and the rest of the GNOME desktop. Its main purpose was to provide a single API for local and remote files and therefore making accessing file on e.g. FTP shares totally transparent to the API user.

Over the years the design of GnomeVFS resulted in lot of different problems and a complicated API and this lead to a very low adoption rate of GnomeVFS within the GNOME Desktop. An additional factor which amplified this was that GnomeVFS depended on lots of different libraries and therefore was very high in the GNOME software stack. These dependencies (e.g. libbonobo) were then also forced on the programs that wanted to use the virtual filesystem abstraction.

As a result, a more user-friendly I/O framework namely GIO, was designed, based on the experience with GnomeVFS, and it was located inside a module of glib, the most fundamental of all GNOME specific libraries.

Brief Reference

The following table provides a quick comparison of the old concepts found in GnomeVFS and their new counterparts now found in GIO with an additional link to the specific section in this document.

Table 1. Conceptual Differences Overview

GnomeVFSGIOSection
GnomeVFSURIGFileFile and Directory Operations
GnomeVFSResultGErrorTypes and Objects
GnomeVFSFileSizegoffsetTypes and Objects
GnomeVFSFileOffsetgoffsetTypes and Objects
GnomeVFSFileInfoGFileInfoFile Information
Mime TypesContent TypesContent Types
GnomeVFSDirectoryHandleGFileEnumeratorNavigating the Filesystem-Tree
GnomeVFSMonitorGFileMonitorMonitoring Files and Directories
GnomeVFSVolumeMonitorGVolumeMonitorMonitoring Files and Directories
GnomeVFSDriveGVolumeMounts, Volumes and Drives
-GDriveMounts, Volumes and Drives


Design differences

GnomeVFS design followed the POSIX API very closely, i.e. it provided abstraction for open(), read(), write(), close(), stat() and friends. It also provided some additional higher level functions, including file monitoring, volume and drive handling and file transfer APIs (e.g. gnome_vfs_xfer());

The main abstraction point and interface to GnomeVFS in this design was the URI: Every GnomeVFS function took an URI, and different remote files where identified by the scheme part of the URI indicating where the remote file is located. For different remote locations (e.g. FTP, Samba, SSH/SFTP) different "methods" provided the functionality.

A result of this was also that GnomeVFS was stateless, i.e. there was no shared context information, even when they accessed the same exact URI, neither between different GnomeVFS using programs nor the different GnomeVFS operations within one single program (except of course GnomeVFSFileHandle when reading or writing to a file). Using the URI as main interface turned out to be a bad idea. The new design of GIO is therefore an evolution of GnomeVFS based on the experience and mistakes made in the latter.

GIO/GVFS Design

GnomeVFS is replaced by two different components: The first component is GIO which is the user visible part that provides a total generic and abstract file system API. GIO lives as a module inside glib (package config: gio-2.0) and thus is at the very bottom of the GTK+/GNOME software stack. GIO itself provides everything needed to handle local files. The second component, GVFS, provides the remote file access functionality over various backends. GVFS also provides other platform specific implementations of generic interfaces of GIO, like volume monitoring and default application handling.

GIO is designed to be a stateful per user session, which means that once a connection to a remote share is established, i.e. the remote location is "mounted", every application that is run by that user in that session can access it. There is no need to authenticate once again. This is achieved by having a separate process which handles remote file access. Interaction with those daemons is done via the dbus IPC mechanism.

Figure 1. GIO/GVFS Architecture Overview

GIO/GVFS Architecture Overview


Types and Objects

When GnomeVFS was designed GObject didn't exist and thus most of the Types in GnomeVFS were plain C structures, some of them introducing their own reference counting scheme. In GIO objects and interfaces were used to guarantee a good integration within the rest of the GNOME Developer Platform.

File and Directory Operations

The the central interface in GIO for interacting with files is the GFile interface. Where in GnomeVFS most functions were operating on URIs, in GIO you request a GFile reference for a given location. All operations on files, including content manipulation, obtaining information about a file, navigating the filesystem hierarchy, transferring files and monitoring the filesystem for changes are all done through this GFile reference. The two major functions for obtaining a new GFile reference from GIO are g_file_new_for_uri () and g_file_new_for_path (). Note, that none of these functions can ever fail. In the case of an malformed path/URI or an unsupported scheme (in the URI case) a dummy GFile object will be returned and any operation on that dummy object will result in an error.

If you request a GFile for a valid but non-existing path a valid GFile object representing that very path will be returned. All navigational functions will work and of course a call to g_file_create() (see Streaming API below).

Handling input from the command line

There is an additional convenience function that is specially designed to handle input from the command line: g_file_new_for_commandline_arg (const char *arg). The Argument (arg) can be a URI, an absolute path or a relative path. The latter is resolved against the current working directory.

Remote locations and mounting

Since GIO is a stateful abstraction layer a remote location must be mounted before it can be used. As a result of this any operation on a GFile references a location that is currently not mounted can fail with the G_IO_ERROR_NOT_MOUNTED error. To mount the volume that encloses the remote location use g_file_mount_enclosing_volume (). This function takes an implementation of the GMountOperation interface which will be used for user interaction, e.g. asking for credentials, during the mount operation. GTK+ provides GtkMountOperation, a generic implementation of that interface, which can be used in GTK+ based GUI applications.

GFile from the GFileChooser

Note that the FileChooser is normally the main entry point for opening files in GUI Applications. It will do all the heavy lifting for you, like mounting remote locations. The GFile you get back from gtk_file_chooser_get_file and friends should therefore in most cases already be mounted and fully usable.

If for some reason there is the need to interact directly with the GMount for a remote location (e.g. manually mounting or unmounting it), the function g_file_find_enclosing_mount is provided by GIO. See Mounts, Volumes and Drives below.

Accessing and Modifying File Content

Accessing the contents of a file is not done via the GFile object directly but by the general purpose Streaming API provided by the GIO, i.e. over separated stream objects. To obtain these stream objects one must use g_file_read() to read the file content, g_file_create() to write to a newly created file or g_file_replace() when replacing the file contents.

Replacing file contents

The function g_file_create () was designed to make replacing the contents of a file easy and safe. To avoid data corruption and race conditions GIO will try to replace the content of the file atomically, with the support of the gvfs backend even on remote files. GIO can, if wanted, automatically create a backup in a safe way before overwriting the content.

With the help of an entity tag (etag), that can be obtained via the file info framework (see File Information below), one can avoid the lost-update problem: when reading the content of the file one remembers the etag. Because the etag changes when the content of the file changes GIO can detect external changes to the file content so updates to the file won't get lost.

File Information

One of the fundamental operations when dealing with files is to get information about the file. This operation was done by calling gnome_vfs_get_file_info() which - on success - filled the GnomeVFSFileInfo struct.

In GIO getting information is a lot more flexible than in GnomeVFS. This has been achieved by replacing GnomeVFSFileInfo struct with the GFileInfo object which stores information and meta-data about the file in various GFileAttributes. The information is obtained by a call to g_file_query_info ().

A GFileAttribute basically is a pair of a key and a value. A key itself consists of a namespace and a keyname, seperated by a double colon: namespace::keyname. There exists a predefined set of namespaces that group together certain strongly related pieces of information and a set of well defined keynames representing specific information about the file (see Table 1 and Table 2 of the GIO GFileAttribute Reference for a complete list). All fundamental information, like which type (i.e file, directory, symlink), lives in the standard:: namespace; all unix specific information in unix:: just to give a few examples.

When obtaining information one can explicitly specify which information should be retrieved by giving a string containing a comma separated list of keys. For well-known keys GIO provides an enumeration, so e.g. G_FILE_ATTRIBUTE_STANDARD_TYPE can be used instead of "standard::type". Also wildcards are supported which means asking for "*" will query for all metadata and asking for "standard::*" will query for all metadata inside the "standard::" namespace.

Names

GIO has several different "names" associated with a single GFile. The normal filename represents the on-disk name of the file, i.e. this is the identifier the filesystem (be it local or e.g. WebDAV) uses to reference this file. This filename might not always be suitable for display. Therefore GIO has standard::display-name file attribute which, if present, should be used when displaying the name to the end-user. It is also always guaranteed to be in UTF-8. There exists also a special standard::edit-name attribute which can be used when renaming the file in the UI. The standard::copy-name is a UTF-8 transcoded version of the filename, which can be useful when copying the file to a different filesystem with a different encoding.

Icons

The standard::icon file attribute contains an object that implements the GIcon interfaces. The most commonly returned object is GThemedIcon. Backends might also supply a GLoadableIcon, which exports a GInputStream with the icon data.

There exist several convenience functions in GTK+ for dealing with GIcons, like gtk_image_new_from_gicon() to create a GtkImage from a given GIcon.

Content Types

Content-Types are a platform specific way of typing the content of a file. On unix it is a mime type, on win32 it is an extension string like ".do", ".txt" or a perceived string like "audio". Such strings can be looked up in the registry at HKEY_CLASSES_ROOT.

Normally the content type is retrieved through the attributes of a GFileInfo but GIO has also API to guess the content type based on a path or filename and some sample data (the sample data is used in combination with a database containing the "magic" sequences of different file types).

Example 1. Obtaining file information

List all attributes stored inside a GFileInfo:
		
static void
show_attributes (GFileInfo *info)
{
  char **attributes;
  char *s;
  int i;
  
  attributes = g_file_info_list_attributes (info, NULL);
  
  g_print ("attributes:\n");
  for (i = 0; attributes[i] != NULL; i++)
    {
      s = g_file_info_get_attribute_as_string (info, attributes[i]);
      g_print ("  %s: %s\n", attributes[i], s);
      g_free (s);
     }
  g_strfreev (attributes);
Query and display file information:
GFileInfo   *info;
const char  *name, *type;
goffset      size;

info = g_file_query_info (file, NULL, 0, NULL, &error);

if (info == NULL)
  /* Handle error */

name = g_file_info_get_display_name (info);
if (name)
  g_print ("display name: %s\n", name);

/* for a function version of type_to_string () 
   see programs/gvfs-info.c in the gvfs source code */
type = type_to_string (g_file_info_get_file_type (info));
g_print ("type: %s\n", type);

size = g_file_info_get_size (info);
g_print ("size: %"G_GUINT64_FORMAT"\n", (guint64)size);

if (g_file_info_get_is_hidden (info))
  g_print ("hidden\n");

show_attributes (info);
						
g_object_unref (info);

Navigating the Filesystem-Tree

Navigating the filesystem in GnomeVFS were, with the exception of enumerating the contents of a directory, done by URI manipulation. Since URIs are in GIO just the entry point to a GFile, all navigations are done with GFiles. The various navigational functions all return GFiles as well.

A special object, the GFileEnumerator is used for listing the contents of a directory. Sample usage of this object is shown below.

Example 2. Enumerate files of a directory

GFileEnumerator *enumerator;
GFileInfo       *info;

enumerator = g_file_enumerate_children (parent, NULL, 0,  NULL,	&error);

if (enumerator == NULL)
  /* Handle error */
					
					
while ((info = g_file_enumerator_next_file (enumerator,
                                            NULL, &error)) != NULL)
  {
    /* Do something with the file info */		
    g_object_unref (info);
  }
	
g_file_enumerator_close (enumerator, NULL, NULL);


File Transfers

GIO provides various high level functions for doing file transfers. There exist functions for copying, moving and deleting files and functions for creating and removing directories. All of these functions do not work recursively (Note that in GIO there is no equivalent to gnome_vfs_xfer which did recursive file transfers. It got removed because it was very hard to use and even harder to maintain.) All file transfer functions are designed in a way that they provide useful error codes in any situation, so they can be used directly without obtaining information about the source or destination. This means that in addition to the standard error codes G_IO_ERROR_IS_DIRECTORY, G_IO_ERROR_NOT_FOUND and G_IO_ERROR_EXISTS, GIO guarantees to return G_IO_ERROR_WOULD_MERGE, when trying to overwrite a directory with a directory, or G_IO_ERROR_WOULD_RECURSE, when the attempted operation would result in a recursion.

For move and copy, which have very similar semantics, there exist flags (GFileCopyFlags) which can alter the behavior of the functions. Important flags e.g. control if a destination is to be overwritten (G_FILE_COPY_OVERWRITE), if symlinks should not be followed (G_FILE_COPY_NOFOLLOW_SYMLINKS) or if a backup of any existing data should be made when overwriting files (G_FILE_COPY_BACKUP).

Copy and move will try to use the specific backend's native function but if the function is not supported (at all or for the current operations, e.g. if you try moving a file across filesystems) or if the source and the destination are on different backends GIO provides fallback codes. This means copying the file through a local buffer (and manually deleting it afterwards in case of a move operation).

Table 2. Error codes for copy and move transfers.

ConditionError Code
Source does not existG_IO_ERROR_NOT_FOUND
Target exists and G_FILE_COPY_OVERWRITE not specifiedG_IO_ERROR_EXISTS
Trying to overwrite a directory with a fileG_IO_ERROR_IS_DIRECTORY
Trying to overwrite a directory with a directoryG_IO_ERROR_WOULD_MERGE
Trying to copy/move a directory and the destination does not exist or G_FILE_COPY_OVERWRITE was specified.G_IO_ERROR_WOULD_RECURSE


Trash Handling

In contrast to GnomeVFS, which had its custom solution, GIO follows the freedesktop Desktop Trash Can Specification. The on-disk location of the trashed files has therefore moved from $HOME/.Trash to $XDG_DATA_HOME/Trash (which normally equals $HOME/.local/share/Trash).

To make trash handling easier for the API users a special function g_file_trash() was added to GIO which will take care of all the details of storing a file in the trash. There is no need to use gnome_vfs_find_directory() with GNOME_VFS_DIRECTORY_KIND_TRASH to find out where to move to anymore.

Access to the content of the trash is still provided by the trash:// location which is implemented by a GVFS backend. Of course all normal GIO operations can be used. In addition to all normal metadata, a special trash::orig-path attribute is set by the trash backend indicating the location where the file was moved from. The icon of the trash can can be obtained via the normal "standard::icon" attribute. This icon will also reflect the current status of the trash can, so it is wise to monitor trash:// for changes.

Streaming API

In GIO all I/O is done by a unified multipurpose streaming API that is similar to the API found in Java or .NET. The two fundamental I/O base classes are GInputStream for reading data from streams and GOutputStream for writing data to streams.

Error checking when closing GOutputStream

It is important to note that closing an GOutputStream is not a trivial operations. Errors can occur and might indicate that the data written to the stream was not written correctly to the destination (e.g. the file on the disk). It is therefore important to always check the result when closing streams that were written to.

Example 3. Reading data from a file

int
main (int argc, char *argv[])
{
  GFileInputStream *stream;
  GFile            *file;
  gssize            res;
  gboolean          res_close;
  GError           *error = NULL;

  if (argc != 2)
    {
      g_printerr ("usage: %s <file>\n", g_get_prgname ());
      return 1;
    }
  
  file = g_file_new_for_commandline_arg (argc[1]);
  
  stream = g_file_read (file, NULL, &error);
  if (stream == NULL)
    {
      g_printerr ("Error opening file %s: %s\n",
                  g_file_get_uri (file), error->message);
      g_error_free (error);
      g_object_unref (file);
      return 1;
    }

  while (1)
    {
      res = g_input_stream_read (G_INPUT_STREAM (stream),
                                 buffer,
                                 sizeof (buffer) - 1,
                                 NULL,
                                 &error);
      if (res < 0)
        /*  Handle error */
      else if (res == 0)
        break; /* EOF */

      /* do something with the data */
    }

   /* NB: Always check errors on closing the stream */
   res_close = g_input_stream_close (G_INPUT_STREAM (stream), NULL, &error);
   if (!res_close)
     /* Handle error */

  g_object_unref (stream);
  g_object_unref (file);
  return 0;
}
	


Read-Write Streams

There exists an additional GIOStream class which represents objects that can be written to and read from. All streaming itself is here also done with the help of GInputStream and GOutputStream which can be requested using g_io_stream_get_input_stream () and g_io_stream_get_output_stream ().

Prominent examples of GIOStream implementations are two-way network connections (GSocketConnection) and files opened in read-write mode (GIOFileStream).

Asynchronous operations

For most I/O operations, be it file or streaming operations, there exists an asynchronous version. Calls to those version are guaranteed to run in the background, will never block and can therefore be executed from within the main thread without the risk of blocking the UI (user interface).

The way to interact with the asynchronous versions has been greatly improved and streamlined in GIO. In GnomeVFS, the async versions of the functions had a parameter that took a for-that-function specific callback. Once the operation was done this callback was called with the various specific return values. The result of this was that lots of different callbacks with different signatures existed.

To get rid of those different callbacks, and thus eliminate the need to supply and implement different callbacks, a single pattern is used for all asynchronous operations: All async versions of the operations consist of two different functions: one function to start the operation (most of them have same name as the sync version of the operation plus a _async suffix) that takes a GAsyncReadyCallback callback together with a gpointer which can hold caller specified data; and one function to finish the operation and retrieve its result.

One starts the async operation by invoking the starter function. Once the operation is completed (or got cancelled) this callback is invoked, which contains a GAsyncResult that is used by the operation to store its result and data. From that callback one invokes the finisher function, supplying the GAsyncResult as parameter, checks for errors and on success retrieves the data of the operation.

Example 4. Async Pattern

The following example demonstrates the GIO async pattern for the async version of a hypothetical function operation, which consists of the starter and finisher functions:
/* The theoretical function "operation" that takes "input"
   as argument, process it (which may take a long time),
   and returns the result to the user.
   Returning NULL indicates an error */

char  *operation           (const char *input);


/* Async version of a theoretical operation, which will start
   the operation, do the work in the background and then call
   "callback" to signal it is finished. */

void   operation_foo_async (Theoretical         *object,
                            const char          *input,
                            GCancellable        *cancellable,
                            GAsyncReadyCallback *callback,
                            gpointer             user_data);


/* Finisher function of the async version.
This function must
   *only* be called from the specific GAsyncReadyCallback
   function to obtain the result. As with the sync version
   NULL will indicate an error. */

char  *operation_foo_finish (Theoretical   *object,
                             GAsyncResult  *res,
                             GError       **error);
The use of the async version of operation, in the form of these two theoretical functions, would then be like this:
	
static void 
operation_foo_ready (GObject      *source_object, 
					 GAsyncResult *res, 
					 gpointer      user_data)
{
  char   *data = NULL;
  GError *error = NULL;

  data = operation_foo_finish (source_object, res, &error);

  if (data != NULL)
    {
      g_printf ("Hurray, the operation succeeded!\n");
    }
  else
   {
     g_printf ("Uh oh, the operation failed!\n");
     /* See error for details in the case */
   }
			
   /* ... */

}

int
main (int argc, void *argv[])
{
  /* ... */

  operation_foo_async (object,
                       "data to process",
                       NULL, /* GCancellable */
                       operation_foo_ready,
                       NULL /* user data */);

  /* ... */
}


Monitoring Files and Directories

To obtain a GFileMonitor for a file or directory, use g_file_monitor if you don't know whether the GFile to monitor is a directory or a file, or otherwise g_file_monitor_file respectively g_file_monitor_directory.

This will give you a GFileMonitor; to get notified about changes to the file or directory you are monitoring, connect to the "changed" signal. The signal will be emitted in the thread-default main context of the thread that the monitor was created in (though if the global default main context is blocked, this may cause notifications to be blocked even if the thread-default context is still running).

Example 5. Monitoring a file or directory

#include <glib/gio.h>
				
static gboolean
file_monitor_callback (GFileMonitor      *monitor,
                       GFile             *child,
                       GFile             *other_file,
                       GFileMonitorEvent  eflags)
{
  g_print ("Monitor Event: File = %s\n", g_file_get_parse_name (child));
				
  switch (eflags)
    {
      // Handle event here
    }

    return TRUE;
}
				
 /* ... */
				
  GFileMonitor *monitor;
  monitor = g_file_monitor (file, G_FILE_MONITOR_NONE, cancellable, error);

  if (monitor == NULL)
     /* Handle error */
				
  g_signal_connect (monitor, "changed", (GCallback) monitor_callback, NULL);


Mounts, Volumes and Drives

GIO provides various objects and functions for handling Volumes, Drives and Mounts:

GMount

The GMount interface stands for user-visible GIO mounts, i.e. filesystems that can be accessed through GIO. This can either be a partition on a harddrive or a filesystem on a DVD mounted under a specific location as well as a remote share on a network volume.

GVolume

A GVolume represents a user-visible object that can be mounted (In GnomeVFS the corresponding object was a GnomeVFSDrive). If a volume is mounted one can obtain the corresponding mount by calling g_volume_get_mount.

GDrive

A GDrive is the representation of a piece of real physical connected hardware, which normally is itself a removable medium (e.g. usb storage) or can contain removable media (CD-, DVD-Drives).

The main interface for enumerating drives, volumes and mounts and also monitoring for changes is the GVolumeMonitor. As with file monitoring, changes are signaled using GObject signals.