Documentation/Maemo 5 Developer Guide/Using Generic Platform Components/GnomeVFS File System

File System - GnomeVFS

Maemo includes a powerful file system framework, GnomeVFS. This framework enables applications to use a vast number of different file access protocols without having to know anything about the underlying details. Some examples of the supported protocols are: local file system, HTTP, FTP and OBEX over Bluetooth.

In practice, this means that all GnomeVFS file access methods are transparently available for both developer and end user just by using the framework for file operations. The API for file handling is also much more flexible than the standard platform offerings. It features, for example, asynchronous reading and writing, MIME type support and file monitoring.

All user-file access should be done with GnomeVFS in maemo applications, because file access can be remote. In fact, many applications that come with the operating system on the Maemo compatible device do make use of GnomeVFS. Access to files not visible to the user should be done directly for performance reasons.

A good hands-on starting point is taking a look at the GnomeVFS example in maemo-examples package. Detailed API information can be found in the GnomeVFS API reference


GnomeVFS Example

This section uses the following example:


In maemo, GnomeVFS also provides some filename case insensitivity support, so that the end users do not have to care about the UNIX filename conventions, which are case-sensitive.

The GnomeVFS interface attempts to provide a POSIX-like interface, so that where with POSIX you would use open(), in GnomeVFS you can use gnome_vfs_open; and instead of write(), you can use gnome_vfs_write, etc. The GnomeVFS function names are sometimes a bit more verbose, but otherwise they attempt to implement the basic API. Some POSIX functions, such as mmap(), are impossible to implement in the user space, but normally this is not a big problem. Also some functions fail to work over network connections and outside the local filesystem.

For a simple example of using the GnomeVFS interface functions, see below.

In order to save and load data, at least the following functions are needed:

  • gnome_vfs_init(): initializes the GnomeVFS library. Needs to be done once at an early stage at program startup.
  • gnome_vfs_shutdown(): frees up resources inside the library and closes it down.
  • gnome_vfs_open(): opens the given URI (explained below) and returns a file handle for that if successful.
  • gnome_vfs_get_file_info(): get information about a file (similar to, but with broader scope than fstat).
  • gnome_vfs_read(): read data from an opened file.
  • gnome_vfs_write(): write data into an opened file.

In order to differentiate between different protocols, GnomeVFS uses Uniform Resource 2 syntax when accessing resources. For example in file:///tmp/somefile.txt, the file:// is the protocol to use, and the rest is the location within that protocol space for the resource or file to manipulate. Protocols can be stacked inside a single URI, and the URI also supports username and password combinations (these are best demonstrated in the GnomeVFS API documentation).

The following simple demonstration will be using local files.

A simple application will be extended in the following ways:

  • Implement the "Open" command by using GnomeVFS with full error checking.
  • The memory will be allocated and freed with g_malloc0() and g_free(), when loading the contents of the file that the user has selected.
  • Data loaded through "Open" will replace the text in the GtkLabel that is in the center area of the HildonWindow. The label will be switched to support Pango simple text markup, which looks a lot like simple HTML.
  • Notification about loading success and failures will be communicated to the user by using a widget called HildonBanner, which will float a small notification dialog (with an optional icon) in the top-right corner for a while, without blocking the application.
  • N.B. Saving into a file is not implemented in this code, as it is a lab exercise (and it is simpler than opening).
  • File loading failures can be simulated by attempting to load an empty file. Since empty files are not wanted, the code will turn this into an error as well. If no empty file is available, one can easily be created with the touch command (under MyDocs, so that the open dialog can find it). It is also possible to attempt to load a file larger than 100 KiB, since the code limits the file size (artificially), and will refuse to load large files.
  • The goto statement should normally be avoided. Team coding guidelines should be checked to see, whether this is an allowed practice. Note how it is used in this example to cut down the possibility of leaked resources (and typing). Another option for this would be using variable finalizers, but not many people know how to use them, or even that they exist. They are gcc extensions into the C language, and you can find more about them by reading gcc info pages (look for variable attributes).
  • Simple GnomeVFS functions are used here. They are all synchronous, which means that if loading the file takes a long time, the application will remain unresponsive during that time. For small files residing in local storage, this is a risk that is taken knowingly. Synchronous API should not be used when loading files over network due to the greater uncertainty.
  • I/O in most cases will be slightly slower than using a controlled approach with POSIX I/O API (controlled meaning that one should know what to use and how). This is a price that has to be paid in order to enable easy switching to other protocols later.

N.B. Since GnomeVFS is a separate library from GLib, you will have to add the flags and library options that it requires. The pkg-config package name for the library is gnome-vfs-2.0.

/**
 * hildon_helloworld-8.c
 *
 * This maemo code example is licensed under a MIT-style license,
 * that can be found in the file called "License" in the same
 * directory as this file.
 * Copyright (c) 2007-2008 Nokia Corporation. All rights reserved.
 *
 * We add file loading support using GnomeVFS. Saving files using
 * GnomeVFS is left as an exercise. We also add a small notification
 * widget (HildonBanner).
 *
 * Look for lines with "NEW" or "MODIFIED" in them.
 */

#include <stdlib.h>
#include <hildon/hildon-program.h>
#include <hildon/hildon-color-button.h>
#include <hildon/hildon-find-toolbar.h>
#include <hildon/hildon-file-chooser-dialog.h>
/* A small notification window widget (NEW). */
#include <hildon/hildon-banner.h>
/* Pull in the GnomeVFS headers (NEW). */
#include <libgnomevfs/gnome-vfs.h>

/* Declare the two slant styles. */
enum {
  STYLE_SLANT_NORMAL = 0,
  STYLE_SLANT_ITALIC
};

/**
 * The application state.
 */
typedef struct {
  gboolean styleUseUnderline;
  gboolean styleSlant;

  GdkColor currentColor;

  /* Pointer to the label so that we can modify its contents when a
     file is loaded by the user (NEW). */
  GtkWidget* textLabel;

  gboolean fullScreen;

  GtkWidget* findToolbar;
  GtkWidget* mainToolbar;
  gboolean findToolbarIsVisible;
  gboolean mainToolbarIsVisible;

  HildonProgram* program;
  HildonWindow* window;
} ApplicationState;

  /*... Listing cut for brevity ...*/

/**
 * Utility function to print a GnomeVFS I/O related error message to
 * standard error (not seen by the user in graphical mode) (NEW).
 */
static void dbgFileError(GnomeVFSResult errCode, const gchar* uri) {
  g_printerr("Error while accessing '%s': %s\n", uri,
             gnome_vfs_result_to_string(errCode));
}

/**
 * MODIFIED (A LOT)
 *
 * We read in the file selected by the user if possible and set the
 * contents of the file as the new Label content.
 *
 * If reading the file fails, the label will be left unchanged.
 */
static void cbActionOpen(GtkWidget* widget, ApplicationState* app) {

  gchar* filename = NULL;
  /* We need to use URIs with GnomeVFS, so declare one here. */
  gchar* uri = NULL;

  g_assert(app != NULL);
  /* Bad things will happen if these two widgets don't exist. */
  g_assert(GTK_IS_LABEL(app->textLabel));
  g_assert(GTK_IS_WINDOW(app->window));

  g_print("cbActionOpen invoked\n");

  /* Ask the user to select a file to open. */
  filename = runFileChooser(app, GTK_FILE_CHOOSER_ACTION_OPEN);
  if (filename) {
    /* This will point to loaded data buffer. */
    gchar* buffer = NULL;
    /* Pointer to a structure describing an open GnomeVFS "file". */
    GnomeVFSHandle* fileHandle = NULL;
    /* Structure to hold information about a "file", initialized to
       zero. */
    GnomeVFSFileInfo fileInfo = {};
    /* Result code from the GnomeVFS operations. */
    GnomeVFSResult result;
    /* Size of the file (in bytes) that we'll read in. */
    GnomeVFSFileSize fileSize = 0;
    /* Number of bytes that were read in successfully. */
    GnomeVFSFileSize readCount = 0;

    g_print("  you chose to load file '%s'\n", filename);

    /* Convert the filename into an GnomeVFS URI. */
    uri = gnome_vfs_get_uri_from_local_path(filename);
    /* We don't need the original filename anymore. */
    g_free(filename);
    filename = NULL;
    /* Should not happen since we got a filename before. */
    g_assert(uri != NULL);
    /* Attempt to get file size first. We need to get information
       about the file and aren't interested in other than the very
       basic information, so we'll use the INFO_DEFAULT setting. */
    result = gnome_vfs_get_file_info(uri, &fileInfo,
                                     GNOME_VFS_FILE_INFO_DEFAULT);
    if (result != GNOME_VFS_OK) {
      /* There was a failure. Print a debug error message and break
         out into error handling. */
      dbgFileError(result, uri);
      goto error;
    }

    /* We got the information (maybe). Let's check whether it
       contains the data that we need. */
    if (fileInfo.valid_fields & GNOME_VFS_FILE_INFO_FIELDS_SIZE) {
      /* Yes, we got the file size. */
      fileSize = fileInfo.size;
    } else {
      g_printerr("Couldn't get the size of file!\n");
      goto error;
    }

    /* By now we have the file size to read in. Check for some limits
       first. */
    if (fileSize > 1024*100) {
      g_printerr("Loading over 100KiB files is not supported!\n");
      goto error;
    }
    /* Refuse to load empty files. */
    if (fileSize == 0) {
      g_printerr("Refusing to load an empty file\n");
      goto error;
    }

    /* Allocate memory for the contents and fill it with zeroes.
       NOTE:
         We leave space for the terminating zero so that we can pass
         this buffer as gchar to string functions and it is
         guaranteed to be terminated, even if the file doesn't end
         with binary zero (odds of that happening are small). */
    buffer = g_malloc0(fileSize+1);
    if (buffer == NULL) {
      g_printerr("Failed to allocate %u bytes for buffer\n",
                 (guint)fileSize);
      goto error;
    }

    /* Open the file.

       Parameters:
       - A pointer to the location where to store the address of the
         new GnomeVFS file handle (created internally in open).
       - uri: What to open (needs to be GnomeVFS URI).
       - open-flags: Flags that tell what we plan to use the handle
         for. This will affect how permissions are checked by the
         Linux kernel. */

    result = gnome_vfs_open(&fileHandle, uri, GNOME_VFS_OPEN_READ);
    if (result != GNOME_VFS_OK) {
      dbgFileError(result, uri);
      goto error;
    }
    /* File opened succesfully, read its contents in. */
    result = gnome_vfs_read(fileHandle, buffer, fileSize,
                            &readCount);
    if (result != GNOME_VFS_OK) {
      dbgFileError(result, uri);
      goto error;
    }
    /* Verify that we got the amount of data that we requested.
       NOTE:
         With URIs it won't be an error to get less bytes than you
         requested. Getting zero bytes will however signify an
         End-of-File condition. */
    if (fileSize != readCount) {
      g_printerr("Failed to load the requested amount\n");
      /* We could also attempt to read the missing data until we have
         filled our buffer, but for simplicity, we'll flag this
         condition as an error. */
      goto error;
    }

    /* Whew, if we got this far, it means that we actually managed to
       load the file into memory. Let's set the buffer contents as
       the new label now. */
    gtk_label_set_markup(GTK_LABEL(app->textLabel), buffer);

    /* That's it! Display a message of great joy. For this we'll use
       a dialog (non-modal) designed for displaying short
       informational messages. It will linger around on the screen
       for a while and then disappear (in parallel to our program
       continuing). */
    hildon_banner_show_information(GTK_WIDGET(app->window),
      NULL, /* Use the default icon (info). */
      "File loaded successfully");

    /* Jump to the resource releasing phase. */
    goto release;

  error:
    /* Display a failure message with a stock icon.
       Please see http://maemo.org/api_refs/4.0/gtk/gtk-Stock-Items.html
       for a full listing of stock items. */
    hildon_banner_show_information(GTK_WIDGET(app->window),
      GTK_STOCK_DIALOG_ERROR, /* Use the stock error icon. */
      "Failed to load the file");

  release:
    /* Close and free all resources that were allocated. */
    if (fileHandle) gnome_vfs_close(fileHandle);
    if (filename) g_free(filename);
    if (uri) g_free(uri);
    if (buffer) g_free(buffer);
    /* Zero them all out to prevent stack-reuse-bugs. */
    fileHandle = NULL;
    filename = NULL;
    uri = NULL;
    buffer = NULL;

    return;
  } else {
    g_print("  you didn't choose any file to open\n");
  }
}

/**
 * MODIFIED (kind of)
 *
 * Function to save the contents of the label (although it doesn't
 * actually save the contents, on purpose). Use gtk_label_get_label
 * to get a gchar pointer into the application label contents
 * (including current markup), then use gnome_vfs_create and
 * gnome_vfs_write to create the file (left as an exercise).
 */
static void cbActionSave(GtkWidget* widget, ApplicationState* app) {
  gchar* filename = NULL;

  g_assert(app != NULL);

  g_print("cbActionSave invoked\n");

  filename = runFileChooser(app, GTK_FILE_CHOOSER_ACTION_SAVE);
  if (filename) {
    g_print("  you chose to save into '%s'\n", filename);
    /* Process saving .. */

    g_free(filename);
    filename = NULL;
  } else {
    g_print("  you didn't choose a filename to save to\n");
  }
}

  /*... Listing cut for brevity ...*/

/**
 * MODIFIED
 *
 * Add support for GnomeVFS (it needs to be initialized before use)
 * and add support for the Pango markup feature of the GtkLabel
 * widget.
 */
int main(int argc, char** argv) {

  ApplicationState aState = {};

  GtkWidget* label = NULL;
  GtkWidget* vbox = NULL;
  GtkWidget* mainToolbar = NULL;
  GtkWidget* findToolbar = NULL;

  /* Initialize the GnomeVFS (NEW). */
  if(!gnome_vfs_init()) {
    g_error("Failed to initialize GnomeVFS-libraries, exiting\n");
  }

  /* Initialize the GTK+ */
  gtk_init(&argc, &argv);

  /* Setup the HildonProgram, HildonWindow and application name. */
  aState.program = HILDON_PROGRAM(hildon_program_get_instance());
  g_set_application_name("Hello Hildon!");
  aState.window = HILDON_WINDOW(hildon_window_new());
  hildon_program_add_window(aState.program,
                            HILDON_WINDOW(aState.window));

  /* Create the label widget, with Pango marked up content (NEW). */
  label = gtk_label_new("<b>Hello</b> <i>Hildon</i> (with Hildon"
                        "<sub>search</sub> <u>and</u> GnomeVFS "
                        "and other tricks<sup>tm</sup>)!");

  /* Allow lines to wrap (NEW). */
  gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);

  /* Tell the GtkLabel widget to support the Pango markup (NEW). */
  gtk_label_set_use_markup(GTK_LABEL(label), TRUE);

  /* Store the widget pointer into the application state so that the
     contents can be replaced when a file will be loaded (NEW). */
  aState.textLabel = label;

  buildMenu(&aState);

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(aState.window), vbox);
  gtk_box_pack_end(GTK_BOX(vbox), label, TRUE, TRUE, 0);

  mainToolbar = buildToolbar(&aState);
  findToolbar = buildFindToolbar(&aState);

  aState.mainToolbar = mainToolbar;
  aState.findToolbar = findToolbar;

  /* Connect the termination signals. */
  g_signal_connect(G_OBJECT(aState.window), "delete-event",
                   G_CALLBACK(cbEventDelete), &aState);
  g_signal_connect(G_OBJECT(aState.window), "destroy",
                   G_CALLBACK(cbActionTopDestroy), &aState);

  /* Show all widgets that are contained by the Window. */
  gtk_widget_show_all(GTK_WIDGET(aState.window));

  /* Add the toolbars to the Hildon Window. */
  hildon_window_add_toolbar(HILDON_WINDOW(aState.window),
                            GTK_TOOLBAR(mainToolbar));
  hildon_window_add_toolbar(HILDON_WINDOW(aState.window),
                            GTK_TOOLBAR(findToolbar));

  /* Register a callback to handle key presses. */
  g_signal_connect(G_OBJECT(aState.window), "key_press_event",
                   G_CALLBACK(cbKeyPressed), &aState);

  g_print("main: calling gtk_main\n");
  gtk_main();
  g_print("main: returned from gtk_main and exiting with success\n");

  return EXIT_SUCCESS;
}

File:Hildon helloworld-8-ok.png

File:Hildon helloworld-8-failed.png

In order to experiment with loading other content, a simple file can be created, containing Pango markup like this: echo "<b>Hello world</b>" > MyDocs/hello.txt , and then loading hello.txt.

As can be imagined, these examples have only scratched the surface of GnomeVFS that is quite a rich library, and contains a broad API and a large amount of plug-ins. Many things have been completely avoided, such as directory content iteration, the asynchronous interface, callback signaling on directory content changes etc. Please see GnomeVFS API reference for more information. The API also contains some mini tutorials on various GnomeVFS topics, so it is well worth the time spent reading. It will also show that GnomeVFS has been overloaded with functions, which are not even file operation related (such as ZeroConf and creating TCP/IP connections etc.).

GTK+ does not have to be used in order to use GnomeVFS. One such example program is Midnight Commander (a Norton Commander clone, but better), which is a menu-based "text" mode program. GnomeVFS uses GLib though, so if using GnomeVFS, one should think about using GLib as well, as it will be loaded anyway.