Category Archives: thesis

Final exams, diplom thesis and thunar-volman

I guess it’s time for an update.

This week I passed the last of four final exams in computer science and human-computer interaction at my university. Not only am I pleased that nine months of learning are over; I am also blessed with excellent grades. And while my understand of good grades is similar to that of money (nice to have it but nothing to focus on and/or brag with), I have to admit that this time I’m at least a little proud of myself. Five years at the university are slowly coming to an end, the first friends are leaving town and it will soon be time to take the next big step in life.

I cannot leave this city before writing another thesis, however. Thus, I am currently looking for a Diplom thesis at the university or at companies related to open source technologies. If you happen work for such a company with interesting thesis ideas or opportunities, please let me know. The thesis is supposed to take about 6 months, ideally starting early in September. After that it’s time to pack my stuff and look for an employer. I’m hoping for a position in open source software development. Areas I’m particularly interested in include Linux, mobile computing, desktop-related technologies as well as renewable energy, environment protection and open government. I’m here to make a difference.

On to something else. A few weeks ago I was offered sponsoring in order to work on thunar-volman for a few days. I accepted the offer, so for the entire next week from 2010-07-19 to 2010-07-26 (including the weekend) I’ll do some sponsored work on porting Thunar and thunar-volman to udev and GIO! The goal is to finish all major features (storage devices, cameras etc.).

Yesterday I did some warm-up hacking on tumbler to verify that if I’m still up to the task. The results: a new ffmpegthumbnailer-based video thumbnailer plugin (written by Lionel Le Folgoc), a new PDF/PostScript thumbnailer plugin based on poppler-glib and a new tumbler release (0.1.2). The master branch contains another commit adding arbitrary URI support to the PDF/PostScript thumbnailer but for that you’ll have to wait until the next release.

That’s it for now, I’ll be a lot more active next week. Take care everyone!

News From Busyland

This is just a short heads up concerning Tumbler. I just merged Philip’s last critical commit to complete support for specialized thumbnailer services into master. We’ll have to give this some testing but I’m quite optimistic that we’ll be able to release 0.1.0 this weekend or next week. A new release of Thunar will follow shortly after that in preparation for 1.2 (to be released along with Xfce 4.8), supporting virtual and remote file systems based on GIO.

I’ve been pretty occupied lately. Aside from learning for my final university exams I finished my short thesis on porting Thunar to GIO. I already got the very positive results back and I’m going to publish the official version of the thesis soon. Unfortunately, being busy has started to cause not-so-positive developments as well. I haven’t had much time to hack on anything lately and my attendence of FOSDEM 2010 is uncertain. I might still go but I failed to organize anything related to Xfce this year, leaving us without a devroom and talks. So it’d be more like a private meetup rather than an organized team trip with the goal to represent Xfce.

Another consequence of me being busy is that Xfce 4.8 might include less features than planned, at least with regards to the ones I had in mind. For now let’s just hope that I’ll find a little more time for hacking the next months. It doesn’t look too well right now but who knows…

Long Overdue Update

I haven't posted anything in a while, but I'm not complaining. The last month I've been mostly busy pretending to be busy. Ok, to be honest, that's not entirely true. Over at Xfce, we have achieved a lot in August:

  • We've moved all Xfce repositories (including the goodies) over to Git. Kudos to Brian for doing most of the work.
  • All Xfce translation updates are now submitted via Transifex. Thanks to the Transifex developers for all the support and for being such a friendly bunch. It amazes me that Dimitris, the founder of Indifex, is also active on our translations mailing list to support people!
  • The migration of the Thunar core to GIO is complete and has been merged into the main development branch. The overall delta was 2.9MB, although admittedly, a big part of that is due to the removal of ThunarVFS, so I can only take credit for about 16,000 of the 24,335 insertions and 6,000 of the 41,356 deletions.
  • We've kicked off the Xfce 4.8 development cycle on August 16th. The schedule and all other details are available on the wiki. Xfce 4.8 is scheduled for April 12th, 2010. Expect development releases of the various core components soon!

Other things I've been planning to blog about but didn't have the time to:

  • Samuel is still busy setting up Buildbot. With less time than he had hoped for at hands this might still take a while.
  • I was provided with a free Linutop 2 in June. I'd like to thank the Linutop company again for this gift! So far, I've only found the time to give it a few test boots but I'm planning to set it up as a Xfce test machine soon. Its limited hardware makes it much better platform than my rather powerful laptop and/or virtual machines for testing the speed and memory demand of Xfce.
  • My thesis is progressing slowly. I think I have at least 3/4 finished now. I'll keep you posted about the result. I'm hoping to kick off the six month learning phase for my final exams before October because a friend of mine is already waiting for me. ;)

Preview: Browsing SFTP with Thunar

It feels like I'm finally getting somewhere. My GIO migration branch of Thunar now generates thumbnails using the D-Bus service provided by Tumbler (which is still in development but almost ready for a release). It has no references to ThunarVFS anymore, except for one function required by Thunarx, the Thunar extension library. The next step will be to prepare thunarx-2 with a ThunarVFS-free API. All plugins shipped with Thunar itself need to be updated. ThunarVFS will be moved out of the Thunar tarball and into its own, so that applications depending on it will still work. And then we only need to fork the HAL based volume monitor from GVfs and of course thunar-volman needs to be rewritten on top of GIO ...

Anyway, just a few moments ago, I did something I thought is worth sharing: I browsed one of our servers remotely over SFTP! Mounting still needs to be done from the terminal but after that, you can simply type in the SSH or SFTP URI and it will display its contents. Thumbnails are generated over D-Bus. Here's a screenshot:

Update

Thunar can now mount remote URIs on demand:

FTP browsing also works:

News Update

A lot of things are going on lately and it turns out I'm way to busy to update my weblog on a regular basis. So, this is just a short post to keep you all updated.

Last two weeks

  • I took a new student job at the university, at the Institute of Theoretical Computer Science, which I love most. I'll be working on haplotyping algorithms.
  • I went to Barcelona and had a great time at the Ubuntu Developer Summit overall. Stephan and I were very productive. One evening during dinner we discussed our development and release process and later started writing it down. The result is a beautiful document that still needs a bit of polishing. I hope it'll serve as a good foundation for the development and release process of Xfce in the future. Several others commented on it and it looks like we'll give the proposed concept a shot after we've switched to git and all that.
  • Even though the week in Barcelona was great for Xfce, I was disappointed with how it went with regards to the cooperation between Xubuntu and Xfce. I talked it through with Stephan and after I got back, I resigned from my position as the Xubuntu Xfce4 Liaison.

At the moment

  • I've started to implement the so-called thumbnail management D-Bus specification. My implementation is called Tumbler and it will hopefully start serving thumbnails for other apps soon. I'm planning to use it in Thunar, Stephan has expressed interest in using it in Ristretto and when discussing the specification and the existing hildon-thumbnail implementation, Philip van Hoof told me that Hildon/Nokia might be interested in dropping hildon-thumbnail in favor of Tumbler if it's flexible enough. I'm working on it as much as I can and I'm hoping to do a release within the next few weeks.
  • I'm still working on the Transifex installation for Xfce. It's already running but I still need to import all the projects, releases and of course create accounts for maintainers automatically.
  • The migration of Xfce to Git is still work in progress. Brian has most of the repository issues sorted out and is now waiting for me to finish the commit mail script. I have two or three possible implementations lying around but I need to have a look at that again.
  • Jim needs the documentation repository next week, so I'm about to set it up.
  • Our Buildbot server has arrived in Sweden. Today I set up the firewall. Samual is taking care of setting up the Buildbot host VM and other things. We're considering to move a lot of the services not related to development (like the Xfce websites) into separate VMs on that server too, in order to make the current server a development-only machine.

Besides that I'm trying to catch up with the work and personal stuff that piled up while I was away last week. And I really should go and sleep.

Another Thunar/GIO Status Update

I'm more or less done with migrating the volume management code in Thunar to GIO. As of today there's not a single reference to ThunarVfsVolume or ThunarVfsVolume manager in Thunar anymore. While that is pretty neat and another thing I can mark as (almost) done, there are a few quirks also:

  • There's no way to find out whether a volume is removable or not. Actually, there are APIs for that but they don't seem to yield reasonable results for any of my USB and DVD drives.
  • GIO doesn't handle repeatedly issued mount requests too well. If you try mounting a DVD volume several times in a row, you'll end up G_IO_ERROR_FAILED errors exposing private D-Bus error messages. That's nothing the user should be bothered with but unfortunately there's no way to avoid this. G_IO_ERROR_FAILED is an error type you normally want to display. I think in this case raising a G_IO_ERROR_PENDING would be more appropriate.

So what's next? I'll just pick the list from my last post because I'm lazy:

  • Move the thumbnail related code into exo.
  • Load ThunarFile objects asynchronously. This will be a pain. A lot of functions will have to be split up to fit into the asynchronous concept.
  • Migrate Thunarx to GIO.
  • Migrate thunar-volman to GIO.
  • Integrate functionality similar to Gigolo (remote/virtual places management) into the file manager.
  • Write GNOME-independent GIO extensions for volume management and the trash.

I'm not so sure I want to load ThunarFiles asynchronously. It'll only work in some situations anyway. However, the possibility of the loading process blocking the GUI is quite high, especially with remote mounts. I guess the next thing I'll work on (next week) is moving thumbnailing out of Thunar and into exo.

Oh, by the way, here are some boring stats of my work in the past four weeks:

 thunar/Makefile.am                      |   44 +-
 thunar/main.c                           |    8 +
 thunar/thunar-abstract-icon-view.c      |    4 -
 thunar/thunar-application.c             |  655 +++++++--------
 thunar/thunar-application.h             |   25 +-
 thunar/thunar-chooser-button.c          |  131 +--
 thunar/thunar-chooser-dialog.c          |  232 +++---
 thunar/thunar-chooser-model.c           |  450 +++-------
 thunar/thunar-chooser-model.h           |   23 +-
 thunar/thunar-clipboard-manager.c       |   53 +-
 thunar/thunar-clipboard-manager.h       |    3 +-
 thunar/thunar-create-dialog.c           |  155 ++--
 thunar/thunar-create-dialog.h           |   25 +-
 thunar/thunar-dbus-service.c            |   47 +-
 thunar/thunar-debug.c                   |    4 -
 thunar/thunar-deep-count-job.c          |  373 ++++++++
 thunar/thunar-deep-count-job.h          |   48 +
 thunar/thunar-details-view.c            |   20 -
 thunar/thunar-dialogs.c                 |  100 ++-
 thunar/thunar-dialogs.h                 |   42 +-
 thunar/thunar-dnd.c                     |   21 +-
 thunar/thunar-enum-types.c              |   57 ++
 thunar/thunar-enum-types.h              |   56 ++
 thunar/thunar-file.c                    | 1455 +++++++++++++++++++++++--------
 thunar/thunar-file.h                    |  309 ++-----
 thunar/thunar-folder.c                  |  192 +++--
 thunar/thunar-gio-extensions.c          |  355 ++++++++
 thunar/thunar-gio-extensions.h          |   63 ++
 thunar/thunar-gtk-extensions.c          |    4 -
 thunar/thunar-icon-factory.c            |   13 +-
 thunar/thunar-io-jobs-util.c            |  139 +++
 thunar/thunar-io-jobs-util.h            |   36 +
 thunar/thunar-io-jobs.c                 | 1074 +++++++++++++++++++++++
 thunar/thunar-io-jobs.h                 |   54 ++
 thunar/thunar-io-scan-directory.c       |  123 +++
 thunar/thunar-io-scan-directory.h       |   37 +
 thunar/thunar-job.c                     | 1001 +++++++++++++++++++++
 thunar/thunar-job.h                     |  112 +++
 thunar/thunar-launcher.c                |  521 +++++++-----
 thunar/thunar-list-model.c              |  155 ++--
 thunar/thunar-location-button.c         |   29 +-
 thunar/thunar-location-buttons.c        |   31 +-
 thunar/thunar-location-entry.c          |  151 +++-
 thunar/thunar-marshal.list              |    4 +
 thunar/thunar-metafile.c                |   42 +-
 thunar/thunar-metafile.h                |    5 +-
 thunar/thunar-path-entry.c              |   65 +-
 thunar/thunar-permissions-chooser.c     |  263 +++---
 thunar/thunar-preferences-dialog.c      |  103 +--
 thunar/thunar-preferences.c             |   71 +-
 thunar/thunar-progress-dialog.c         |  152 ++--
 thunar/thunar-progress-dialog.h         |    7 +-
 thunar/thunar-properties-dialog.c       |  108 ++--
 thunar/thunar-renamer-dialog.c          |   20 +-
 thunar/thunar-renamer-model.c           |   11 +-
 thunar/thunar-sendto-model.c            |  196 ++---
 thunar/thunar-shortcuts-icon-renderer.c |   45 +-
 thunar/thunar-shortcuts-model.c         |  433 +++++-----
 thunar/thunar-shortcuts-view.c          |  594 +++++++++----
 thunar/thunar-simple-job.c              |  223 +++++
 thunar/thunar-simple-job.h              |   64 ++
 thunar/thunar-size-label.c              |   81 +-
 thunar/thunar-standard-view.c           |  212 +++---
 thunar/thunar-templates-action.c        |    7 +-
 thunar/thunar-text-renderer.c           |   19 -
 thunar/thunar-transfer-job.c            |  888 +++++++++++++++++++
 thunar/thunar-transfer-job.h            |   60 ++
 thunar/thunar-trash-action.c            |    9 +-
 thunar/thunar-tree-model.c              |  280 ++++---
 thunar/thunar-tree-view.c               |  730 ++++++++++------
 thunar/thunar-user.c                    |  833 ++++++++++++++++++
 thunar/thunar-user.h                    |   88 ++
 thunar/thunar-util.c                    |  105 ++-
 thunar/thunar-util.h                    |   16 +-
 thunar/thunar-window.c                  |  274 +++---
 75 files changed, 10491 insertions(+), 3947 deletions(-)

The overall size of the patch is 844kb already.

Thunar/GIO – Quick Status Report

I've started hacking on the migration of Thunar to GIO on April 9th. In about 61 commits, I've reduced the original number of ThunarVFS references in the Thunar source code dramatically. The most important and probably most time-consuming part of this work is only mentioned briefly on those pages: rewriting all recursive copy/move/trash/restore/chmod/chgrp/chown jobs -- by now most of the jobs have been rewritten based on GIO and the new ThunarJob framework ... and Thunar still works (for me at least)!

All in all, I suppose that about a third of the implementation work is done. Ok, maybe just a quarter, but an important one. I now have a very good overview over the source code and I'm almost done with one of the most critical parts of the migration.

If you want to know how that looks like for me, here's a screenshot:

Lunar Linux 20090423

These are the most important/big things that are still waiting for me:

  • Replace ThunarVFSVolumeManager with GFileMonitor. Volume management is a large and complex subsystem of both ThunarVFS and GIO.
  • Load ThunarFile objects asynchronously. This will be a pain. A lot of functions will have to be split up to fit into the asynchronous concept.
  • Move the thumbnail related code into exo

These two are a bit out of scope but very important nonetheless:

  • Integrate functionality similar to Gigolo (remote/virtual places management) into the file manager.
  • Write GNOME-independent GIO extensions for volume management and the trash.

On a side node: Xubuntu 9.04 is available as of today! Go and grab it if you're interested in a nice distro based on Xfce. If you're interested in the Xubuntu development, you'll be able to meet Stephan, several Xubuntu folks and me at the Ubuntu Developer Summit in Barcelona from May 25th-29th. I'm very excited already!

Writing Your Own GIO Jobs

After having moved into a new apartment I'm now back at working on my thesis about Thunar. There are a few things which are solved very differently in GIO than in ThunarVFS. One of them is the way jobs are handled. A job basically is a task which may take a while to run and thus is executed in a separate thread (so it doesn't block the GUI).

ThunarVFS has a framework called ThunarVfsJob. It lets you create different kinds of jobs e.g. for changing file permissions recursively or for computing the total number of files and the total size of a directory. These jobs report back to the GUI thread using signals such as "new-files" (when new files are added and need to picked up by the GUI) or "progress".

GIO has something similar ... but it's not so obvious how it works. When I tried to figure out how to migrate ThunarVfsJob to GIO I thought: hey, GIO must have something like this already! It contains several job-like functions such as g_file_copy_async() after all.

So here's what I found out after spending some time on reading gfile.c and glocalfile.c: there is a job framework in GIO ... but it's hidden behind easy-to-use asynchronous functions. It's based on GCancellable, GAsyncResult and a bunch of callback types. It uses GIOScheduler internally to glue everything together to something that is actually pretty convenient (but still kinda tricky).

So, what do you need in orderto write your own jobs in the GIO style?

First of all, you need an example task. I picked counting files and computing the total size of a directory to understand how it works. What we want is an asynchronous function which does exactly that and uses a callback to report the progress back to the GUI thread ... just like g_file_copy_async() does.

First of all, you define the callback type and two functions for starting the job (sync and async version):

The Public API

typedef void (*GFileCountProgressCallback) (goffset  current_num_files,
                                            goffset  current_num_bytes,
                                            gpointer user_data);

static gboolean 
g_file_deep_count (GFile                     *file,
                   GCancellable              *cancellable,
                   GFileCountProgressCallback progress_callback,
                   gpointer                   progress_callback_data,
                   GError                   **error);

 static void
 g_file_deep_count_async (GFile                     *file,
                          int                        io_priority,
                          GCancellable              *cancellable,
                          GFileCountProgressCallback progress_callback,
                          gpointer                   progress_callback_data,
                          GAsyncReadyCallback        callback,
                          gpointer                   callback_data);

The Implementation

All the function g_file_deep_count_async() will do is to create a GSimpleAsyncResult, put the callback information into it and then tell the GIOScheduler to run the job. Here's how that looks like:

static void 
g_file_deep_count_async (GFile                     *file,
                         int                        io_priority,
                         GCancellable              *cancellable,
                         GFileCountProgressCallback progress_callback,
                         gpointer                   progress_callback_data,
                         GAsyncReadyCallback        callback,
                         gpointer                   callback_data)
{
  GSimpleAsyncResult *result;
  DeepCountAsyncData *data;

  g_return_if_fail (G_IS_FILE (file));

  data = g_new0 (DeepCountAsyncData, 1);
  data->file = g_object_ref (file);
  data->progress_cb = progress_callback;
  data->progress_cb_data = progress_callback_data;

  result = g_simple_async_result_new (G_OBJECT (file), 
                                      callback,
                                      callback_data, 
                                      g_file_deep_count_async);
  g_simple_async_result_set_op_res_gpointer (result, 
                                             data, 
                                             (GDestroyNotify) deep_count_async_data_free); 

  g_io_scheduler_push_job (deep_count_async_thread,
                           result, 
                           g_object_unref, 
                           io_priority, 
                           cancellable);
}

DeepCountAsyncData is a simple struct which needs no further explanation, I think. First data with callback and user data information is added to the GSimpleAsyncResult and then the job is added to the GIOScheduler. As you can see, there is another function involved: deep_count_async_thread. This is the function which runs in a separate thread and does most of the work (well, not quite ... but almost). Here's how it looks like:

static gboolean
deep_count_async_thread (GIOSchedulerJob *job,
                         GCancellable    *cancellable,
                         gpointer         user_data)
{
  GSimpleAsyncResult *res;
  DeepCountAsyncData *data;
  gboolean            result;
  GError             *error = NULL;

  res = user_data;
  data = g_simple_async_result_get_op_res_gpointer (res);

  data->job = job;
  result = g_file_deep_count (data->file, 
                              cancellable, 
                              data->progress_cb != NULL ? deep_count_async_progress_callback : NULL, 
                              data, 
                              &error);

  if (data->progress_cb != NULL)
    g_io_scheduler_job_send_to_mainloop (job, (GSourceFunc) gtk_false, NULL, NULL);

  if (!result && error != NULL)
    {
      g_simple_async_result_set_from_error (res, error);
      g_error_free (error);
    }

  g_simple_async_result_complete_in_idle (res);

  return FALSE;
}

As you can see it runs the synchronous function g_file_deep_count() and makes sure the progress callback is called at least once. It does one more thing though: it defines it's own progress callback: deep_count_async_progress_callback. This is required for the real progress callback to be called inside the GUI thread. This is the code for the internal callback:

static gboolean
deep_count_async_progress_in_main (gpointer user_data)
{
  ProgressData       *progress = user_data;
  DeepCountAsyncData *data = progress->data;

  data->progress_cb (progress->current_num_files, 
                     progress->current_num_bytes, 
                     data->progress_cb_data);

  return FALSE;
}

static void
deep_count_async_progress_callback (goffset  current_num_files,
                                    goffset  current_num_bytes,
                                    gpointer user_data)
{
  DeepCountAsyncData *data = user_data;
  ProgressData       *progress;

  progress = g_new (ProgressData, 1);
  progress->data = data;
  progress->current_num_files = current_num_files;
  progress->current_num_bytes = current_num_bytes;

  g_io_scheduler_job_send_to_mainloop_async (data->job, 
                                             deep_count_async_progress_in_main, 
                                             progress, 
                                             g_free);
}

deep_count_async_progress_callback() is called from within the job thread. It then tells the scheduler to call deep_count_async_progress_in_main from the GUI thread. And finally deep_count_async_progress_in_main calls the real progress callback e.g. to update the GUI.

Now you still haven't seen any code related to counting files and computing the total file size of a directory ... let's get to that now. Here's the synchronous deep count function which is called from within the job thread:

static gboolean  
g_file_deep_count (GFile                     *file,
                   GCancellable              *cancellable,
                   GFileCountProgressCallback progress_callback,
                   gpointer                   progress_callback_data,
                   GError                   **error)
{
  ProgressData data = {
    .data = NULL,
    .current_num_files = 0,
    .current_num_bytes = 0,
  };

  g_return_val_if_fail (G_IS_FILE (file), FALSE); 

  if (g_cancellable_set_error_if_cancelled (cancellable, error))
    return FALSE;

  return g_file_real_deep_count (file, 
                                 cancellable, 
                                 progress_callback, 
                                 progress_callback_data, 
                                 &data, 
                                 error);
}

Damn ... it still doesn't do any real work! Ok, but this time there's no big rat-tail of nested function calls anymore, I promise. There's just one function left: g_file_real_deep_count(). Before we can call it, however, g_file_deep_count() has to initialize the progress data. After that we can call g_file_real_deep_count() recursively and do something useful. Here we go:

static gboolean
g_file_real_deep_count (GFile                     *file,
                        GCancellable              *cancellable,
                        GFileCountProgressCallback progress_callback,
                        gpointer                   progress_callback_data,
                        ProgressData              *progress_data,
                        GError                   **error)
{
  GFileEnumerator *enumerator;
  GFileInfo       *info;
  GFileInfo       *child_info;
  GFile           *child;
  gboolean         success = TRUE;
  
  g_return_val_if_fail (G_IS_FILE (file), FALSE);
  if (g_cancellable_set_error_if_cancelled (cancellable, error))
    return FALSE;

  info = g_file_query_info (file, 
                            "standard::*", 
                            G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 
                            cancellable, 
                            error);

  if (g_cancellable_is_cancelled (cancellable))
    return FALSE;

  if (info == NULL)
    return FALSE;

  progress_data->current_num_files += 1;
  progress_data->current_num_bytes += g_file_info_get_size (info);

  if (progress_callback != NULL)
    {
      /* Here we call the internal callback */
      progress_callback (progress_data->current_num_files, 
                         progress_data->current_num_bytes, 
                         progress_callback_data);
    }

  if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
    {
      enumerator = g_file_enumerate_children (file, 
                                              "standard::*", 
                                              G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, 
                                              cancellable, 
                                              error);
    
      if (!g_cancellable_is_cancelled (cancellable))
        {
          if (enumerator != NULL)
            {
              while (!g_cancellable_is_cancelled (cancellable) && success)
                {
                  child_info = g_file_enumerator_next_file (enumerator, 
                                                            cancellable, 
                                                            error);

                  if (g_cancellable_is_cancelled (cancellable))
                    break;

                  if (child_info == NULL)
                    {
                      if (*error != NULL)
                        success = FALSE;
                      break;
                    }

                  child = g_file_resolve_relative_path (file, g_file_info_get_name (child_info));
                  success = success && g_file_real_deep_count (child, 
                                                               cancellable, 
                                                               progress_callback, 
                                                               progress_callback_data, 
                                                               progress_data, 
                                                               error);
                  g_object_unref (child);
                  g_object_unref (child_info);
                }

              g_object_unref (enumerator);
            }
        }
    }

  g_object_unref (info);

  return !g_cancellable_is_cancelled (cancellable) && success;
}

And that's it. We can now compute the number of files and the total size of a directory recursively using a GCancellable and one or two callbacks. All of this is done using threads, so you don't have to worry about blocking your GUI main loop.

If you want to see this in action, visit the job framework page in my thesis wiki and download deepcount.c and the Makefile.