Re: gio, gvfs and nautilus fixes



And here are patches!

On Thu, 2008-02-21 at 02:46 -0500, David Zeuthen wrote:
> Hey,
> 
> Here's a couple of patches for gio, gvfs and Nautilus. The description
> is in the ChangeLog for each module. Some comments
> 
>  - I'm unsure my fix for nautilus_file_get_mount() is correct and I'm
>    not sure what it's supposed to do. My assumption is that it will
>    return a GMount for every file on a GMount. The older version only
>    did this for the root folder. My fix is provably not correct; we
>    may not have created NautilusFile objects to get to the root. One way
>    of testing this is to have some removable media with a DCIM/
>    directory in the root; for every folder you go into you should see
>    a cluebar. Without this fix you will only see it for the root folder
>    of the mount.
> 
>  - Autorunning was broken; this patch unbreaks it by moving the call
>    to inhibit into only selected call sites. One thing we can't fix
>    is when you mount from computer:// because it runs in another
>    process. Ideas on how to fix this?
> 
>  - g_file_find_enclosing_mount() for GDaemonFile was broken. We just
>    cannot construct a new GMount on the fly as it won't have the
>    the reference to a foreign GVolume that adopted it. My attempt
>    to fix this involves a global variable; it's probably safe as
>    GDaemonVolumeMonitor is a singleton anyway. It's not pretty though.
>    But it works.
> 
>  - Before the feature freeze I commited half of the gphoto2 support
>    namely the bits in the volume monitor to create, maintain and
>    destroy GVolume for connected cameras. This patch is the second
>    half, the actual filesystem code. It's lacking write support
>    at the moment but is otherwise in pretty good shape (barring
>    crashes in libgphoto2; it doesn't like my Sandisk Sansa MTP
>    device that much).
> 
>    So this part of the patch set falls into a grey area wrt feature
>    freeze; technically it's just a bug fix (shipping gvfs without
>    it will appear broken as you can see the GVolume objects for
>    the cameras.. but you have no filesystem code for it!) but I guess
>    asking the release team whether it's OK to commit may be in order.
>    Shrug.
> 
>  - For thumbnailing gvfs mounts you need a libgnomeui patch in
>    http://bugzilla.gnome.org/show_bug.cgi?id=517276
>    There's a few cosmetic details that needs to be cleaned up but
>    by and far it works very well. Bastien says a gstreamer with
>    gio support release is imminent - when that happens one should
>    be able to preview audio from cdda:// mounts!
> 
> More details in the ChangeLog's. Thanks for reviewing this.
> 
>      David
> 
> $ diffstat gio-fixes.patch
>  ChangeLog             |   39 ++++++++++++
>  gcontenttype.c        |   36 ++++++++++-
>  gfile.c               |  162 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  gfile.h               |   24 ++++++-
>  gfileinfo.h           |   26 ++++++++
>  gio.symbols           |    3 
>  glocalfileinfo.c      |   59 ++++++------------
>  gthemedicon.c         |   22 ++++++
>  gthemedicon.h         |    1 
>  gunionvolumemonitor.c |   48 ++++++++------
>  gvolumemonitor.c      |    2 
>  gvolumemonitor.h      |    2 
>  12 files changed, 359 insertions(+), 65 deletions(-)
> 
> $ diffstat gvfs-fixes.patch
>  ChangeLog                     |   43 +
>  client/gdaemonfile.c          |    7 
>  client/gdaemonvolumemonitor.c |   28 
>  client/gdaemonvolumemonitor.h |    3 
>  configure.ac                  |   38 +
>  daemon/Makefile.am            |   24 
>  daemon/gphoto2.mount.in       |    4 
>  daemon/gvfsbackendcdda.c      |  168 ++++-
>  daemon/gvfsbackendgphoto2.c   | 1405 ++++++++++++++++++++++++++++++++++++++++++
>  daemon/gvfsbackendgphoto2.h   |   51 +
>  hal/ghaldrive.c               |   47 -
>  hal/ghalmount.c               |   40 +
>  hal/ghalvolume.c              |  113 ++-
>  hal/ghalvolumemonitor.c       |   52 +
>  hal/ghalvolumemonitor.h       |    5 
>  hal/hal-pool.c                |    6 
>  16 files changed, 1970 insertions(+), 64 deletions(-)
> 
> $ diffstat nautilus-fixes.patch
>  ChangeLog                                        |   48 +++++
>  libnautilus-private/nautilus-autorun.c           |   16 +
>  libnautilus-private/nautilus-directory-async.c   |  214 ++++++++++++++++++++++-
>  libnautilus-private/nautilus-directory-private.h |    6 
>  libnautilus-private/nautilus-file-attributes.h   |    1 
>  libnautilus-private/nautilus-file-operations.c   |    1 
>  libnautilus-private/nautilus-file-private.h      |    4 
>  libnautilus-private/nautilus-file.c              |   97 +++++++++-
>  libnautilus-private/nautilus-file.h              |    1 
>  src/file-manager/fm-directory-view.c             |    8 
>  src/file-manager/fm-icon-view.c                  |   20 +-
>  src/nautilus-places-sidebar.c                    |    2 
>  src/nautilus-window-manage-views.c               |    2 
>  13 files changed, 395 insertions(+), 25 deletions(-)
> 
> 
Index: src/nautilus-window-manage-views.c
===================================================================
--- src/nautilus-window-manage-views.c	(revision 13789)
+++ src/nautilus-window-manage-views.c	(working copy)
@@ -1405,7 +1405,7 @@
 
 		mount = nautilus_file_get_mount (file);
 		if (mount != NULL) {
-			x_content_types = nautilus_autorun_get_x_content_types_for_mount (mount, FALSE);
+			x_content_types = nautilus_autorun_get_x_content_types_for_mount (mount, TRUE);
 			if (x_content_types != NULL && x_content_types[0] != NULL) {
 				nautilus_window_show_x_content_bar (window, mount, x_content_types);
 			}
Index: src/file-manager/fm-icon-view.c
===================================================================
--- src/file-manager/fm-icon-view.c	(revision 13789)
+++ src/file-manager/fm-icon-view.c	(working copy)
@@ -1888,7 +1888,10 @@
 should_preview_sound (NautilusFile *file)
 {
 	GFile *location;
+	GFilesystemPreviewType use_preview;
 
+	use_preview = nautilus_file_get_filesystem_use_preview (file);
+
 	location = nautilus_file_get_location (file);
 	if (g_file_has_uri_scheme (location, "burn")) {
 		g_object_unref (location);
@@ -1902,10 +1905,23 @@
 	}
 		
 	if (preview_sound_auto_value == NAUTILUS_SPEED_TRADEOFF_ALWAYS) {
+		if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) {
+			return FALSE;
+		} else {
+			return TRUE;
+		}
+	}
+	
+	if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) {
+		/* file system says to never preview anything */
+		return FALSE;
+	} else if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL) {
+		/* file system says we should treat file as if it's local */
 		return TRUE;
+	} else {
+		/* only local files */
+		return nautilus_file_is_local (file);
 	}
-	
-	return nautilus_file_is_local (file);
 }
 
 static int
Index: src/file-manager/fm-directory-view.c
===================================================================
--- src/file-manager/fm-directory-view.c	(revision 13789)
+++ src/file-manager/fm-directory-view.c	(working copy)
@@ -7552,7 +7552,9 @@
          * well as doing a call when ready), in case external forces
          * change the directory's file metadata.
 	 */
-	attributes = NAUTILUS_FILE_ATTRIBUTE_METADATA;
+	attributes = 
+		NAUTILUS_FILE_ATTRIBUTE_METADATA |
+		NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO;
 	view->details->metadata_for_directory_as_file_pending = TRUE;
 	view->details->metadata_for_files_in_directory_pending = TRUE;
 	nautilus_file_call_when_ready
@@ -7568,7 +7570,9 @@
 	/* If capabilities change, then we need to update the menus
 	 * because of New Folder, and relative emblems.
 	 */
-	attributes = NAUTILUS_FILE_ATTRIBUTE_INFO;
+	attributes = 
+		NAUTILUS_FILE_ATTRIBUTE_INFO;
+		NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO;
 	nautilus_file_monitor_add (view->details->directory_as_file,
 				   &view->details->directory_as_file,
 				   attributes);
Index: src/nautilus-places-sidebar.c
===================================================================
--- src/nautilus-places-sidebar.c	(revision 13789)
+++ src/nautilus-places-sidebar.c	(working copy)
@@ -1314,6 +1314,7 @@
 		GVolume *volume;
 		gtk_tree_model_get (model, &iter, PLACES_SIDEBAR_COLUMN_VOLUME, &volume, -1);
 		if (volume != NULL) {
+			nautilus_inhibit_autorun_for_volume (volume);
 			nautilus_file_operations_mount_volume (NULL, volume);
 			g_object_unref (volume);
 		}
@@ -1429,6 +1430,7 @@
 			    -1);
 
 	if (volume != NULL) {
+		nautilus_inhibit_autorun_for_volume (volume);
 		nautilus_file_operations_mount_volume (NULL, volume);
 		g_object_unref (volume);
 	}
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 13789)
+++ ChangeLog	(working copy)
@@ -1,3 +1,51 @@
+2008-02-21  David Zeuthen  <davidz redhat com>
+
+	* libnautilus-private/nautilus-autorun.c: (should_autorun_mount):
+	Use g_volume_should_automount() to determine if we should autorun
+	programs on a mount.
+
+	* libnautilus-private/nautilus-directory-async.c:
+	(nautilus_directory_set_up_request),
+	(nautilus_async_destroying_file), (lacks_filesystem_info),
+	(wants_filesystem_info), (request_is_satisfied),
+	(directory_count_start), (mime_list_start),
+	(filesystem_info_cancel), (filesystem_info_stop),
+	(filesystem_info_state_free), (got_filesystem_info),
+	(query_filesystem_info_callback), (filesystem_info_start),
+	(start_or_stop_io), (nautilus_directory_cancel),
+	(cancel_filesystem_info_for_file), (cancel_loading_attributes),
+	(nautilus_directory_cancel_loading_file_attributes):
+	* libnautilus-private/nautilus-directory-private.h:
+	* libnautilus-private/nautilus-file-attributes.h: Add new
+	filesystem info attribute; right now two bits in NautilusFile's
+	private structure are set; the filesystem::use-preview and
+	filesystem:readonly.
+
+	* libnautilus-private/nautilus-file-operations.c:
+	(nautilus_file_operations_mount_volume):
+	* src/nautilus-places-sidebar.c: (open_selected_bookmark),
+	(mount_shortcut_cb): Don't inhibit automount from
+	nautilus_file_operations_mount_volume() because then everything
+	will be inhibited as the mount_added callback use it as
+	well. Rather, move the call to inhibit out to the proper call
+	sites.
+
+	* libnautilus-private/nautilus-file-private.h:
+	* libnautilus-private/nautilus-file.c:
+	(nautilus_file_get_filesystem_use_preview),
+	(nautilus_file_should_show_thumbnail),
+	(get_speed_tradeoff_preference_for_file),
+	(nautilus_file_get_mount):
+	* libnautilus-private/nautilus-file.h:
+	* src/file-manager/fm-directory-view.c: (load_directory):
+	* src/file-manager/fm-icon-view.c: (should_preview_sound):
+	* src/nautilus-window-manage-views.c: (update_for_new_location):
+	Use the new filesystem::use-preview hint to determine if we should
+	preview non-native files (such as gphoto2:// and cdda://
+	mounts). Also try to fix nautilus_file_get_mount() so we always
+	get a GMount - this is needed to display the x-content/* cluebar
+	on every subdirectory of a GMount.
+
 2008-02-20  Cosimo Cecchi  <cosimoc gnome org>
 
 	* libnautilus-private/nautilus-mime-application-chooser.c:
Index: libnautilus-private/nautilus-directory-async.c
===================================================================
--- libnautilus-private/nautilus-directory-async.c	(revision 13789)
+++ libnautilus-private/nautilus-directory-async.c	(working copy)
@@ -89,6 +89,12 @@
 	NautilusFile *file;
 };
 
+struct FilesystemInfoState {
+	NautilusDirectory *directory;
+	GCancellable *cancellable;
+	NautilusFile *file;
+};
+
 struct DirectoryLoadState {
 	NautilusDirectory *directory;
 	GCancellable *cancellable;
@@ -628,6 +634,10 @@
 		request->mount = TRUE;
 		request->file_info = TRUE;
 	}
+
+	if (file_attributes & NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO) {
+		request->filesystem_info = TRUE;
+	}
 }
 
 static void
@@ -1581,6 +1591,12 @@
 		directory->details->mount_state->file = NULL;
 		changed = TRUE;
 	}
+
+	if (directory->details->filesystem_info_state != NULL &&
+	    directory->details->filesystem_info_state->file == file) {
+		directory->details->filesystem_info_state->file = NULL;
+		changed = TRUE;
+	}
 	
 	/* Let the directory take care of the rest. */
 	if (changed) {
@@ -1645,12 +1661,24 @@
 }
 
 static gboolean
+lacks_filesystem_info (NautilusFile *file)
+{
+	return !file->details->filesystem_info_is_up_to_date;
+}
+
+static gboolean
 wants_info (const Request *request)
 {
 	return request->file_info;
 }
 
 static gboolean
+wants_filesystem_info (const Request *request)
+{
+	return request->filesystem_info;
+}
+
+static gboolean
 lacks_deep_count (NautilusFile *file)
 {
 	return file->details->deep_counts_status != NAUTILUS_REQUEST_DONE;
@@ -1798,6 +1826,12 @@
 		}
 	}
 
+	if (request->filesystem_info) {
+		if (has_problem (directory, file, lacks_filesystem_info)) {
+			return FALSE;
+		}
+	}
+
 	if (request->top_left_text) {
 		if (has_problem (directory, file, lacks_top_left)) {
 			return FALSE;
@@ -2606,10 +2640,15 @@
 	
 	directory->details->count_in_progress = state;
 	
+	location = nautilus_file_get_location (file);
 #ifdef DEBUG_LOAD_DIRECTORY		
-	g_message ("load_directory called to get shallow file count for %s", uri);
+	{
+		char *uri;
+		uri = g_file_get_uri (location);
+		g_message ("load_directory called to get shallow file count for %s", uri);
+		g_free (uri);
+	}
 #endif
-	location = nautilus_file_get_location (file);
 
 	g_file_enumerate_children_async (location,
 					 G_FILE_ATTRIBUTE_STANDARD_NAME ","
@@ -3137,11 +3176,16 @@
 
 	directory->details->mime_list_in_progress = state;
 
+	location = nautilus_file_get_location (file);
 #ifdef DEBUG_LOAD_DIRECTORY		
-	g_message ("load_directory called to get MIME list of %s", uri);
+	{
+		char *uri;
+		uri = g_file_get_uri (location);
+		g_message ("load_directory called to get MIME list of %s", uri);
+		g_free (uri);
+	}
 #endif	
 	
-	location = nautilus_file_get_location (file);
 	g_file_enumerate_children_async (location,
 					 G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
 					 0, /* flags */
@@ -3936,7 +3980,7 @@
 		g_object_unref (file->details->mount);
 		file->details->mount = NULL;
 	}
-	
+
 	file->details->mount_is_up_to_date = TRUE;
 	if (mount) {
 		file->details->mount = g_object_ref (mount);
@@ -4080,6 +4124,147 @@
 }
 
 static void
+filesystem_info_cancel (NautilusDirectory *directory)
+{
+	if (directory->details->filesystem_info_state != NULL) {
+		g_cancellable_cancel (directory->details->filesystem_info_state->cancellable);
+		directory->details->filesystem_info_state->directory = NULL;
+		directory->details->filesystem_info_state = NULL;
+		async_job_end (directory, "filesystem info");
+	}
+}
+
+static void
+filesystem_info_stop (NautilusDirectory *directory)
+{
+	NautilusFile *file;
+
+	if (directory->details->filesystem_info_state != NULL) {
+		file = directory->details->filesystem_info_state->file;
+
+		if (file != NULL) {
+			g_assert (NAUTILUS_IS_FILE (file));
+			g_assert (file->details->directory == directory);
+			if (is_needy (file,
+				      lacks_filesystem_info,
+				      wants_filesystem_info)) {
+				return;
+			}
+		}
+
+		/* The filesystem info is not wanted, so stop it. */
+		filesystem_info_cancel (directory);
+	}
+}
+
+static void
+filesystem_info_state_free (FilesystemInfoState *state)
+{
+	g_object_unref (state->cancellable);
+	g_free (state);
+}
+
+static void
+got_filesystem_info (FilesystemInfoState *state, GFileInfo *info)
+{
+	NautilusDirectory *directory;
+	NautilusFile *file;
+
+	/* careful here, info may be NULL */
+
+	directory = nautilus_directory_ref (state->directory);
+
+	state->directory->details->filesystem_info_state = NULL;
+	async_job_end (state->directory, "filesystem info");
+	
+	file = nautilus_file_ref (state->file);
+
+	file->details->filesystem_info_is_up_to_date = TRUE;
+	if (info != NULL) {
+		file->details->filesystem_use_preview = 
+			g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW);
+		file->details->filesystem_readonly = 
+			g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY);
+	}
+	
+	nautilus_directory_async_state_changed (directory);
+	nautilus_file_changed (file);
+	
+	nautilus_file_unref (file);
+	
+	nautilus_directory_unref (directory);
+	
+	filesystem_info_state_free (state);
+}
+
+static void
+query_filesystem_info_callback (GObject *source_object,
+				GAsyncResult *res,
+				gpointer user_data)
+{
+	GFileInfo *info;
+	FilesystemInfoState *state;
+
+	state = user_data;
+	if (state->directory == NULL) {
+		/* Operation was cancelled. Bail out */
+		filesystem_info_state_free (state);
+		return;
+	}
+
+	info = g_file_query_filesystem_info_finish (G_FILE (source_object), res, NULL);
+
+	got_filesystem_info (state, info);
+
+	if (info != NULL) {
+		g_object_unref (info);
+	}
+}
+
+static void
+filesystem_info_start (NautilusDirectory *directory,
+		       NautilusFile *file,
+		       gboolean *doing_io)
+{
+	GFile *location;
+	FilesystemInfoState *state;
+
+	if (directory->details->filesystem_info_state != NULL) {
+		*doing_io = TRUE;
+		return;
+	}
+
+	if (!is_needy (file,
+		       lacks_filesystem_info,
+		       wants_filesystem_info)) {
+		return;
+	}
+	*doing_io = TRUE;
+
+	if (!async_job_start (directory, "filesystem info")) {
+		return;
+	}
+	
+	state = g_new0 (FilesystemInfoState, 1);
+	state->directory = directory;
+	state->file = file;
+	state->cancellable = g_cancellable_new ();
+
+	location = nautilus_file_get_location (file);
+	
+	directory->details->filesystem_info_state = state;
+
+	g_file_query_filesystem_info_async (location,
+					    G_FILE_ATTRIBUTE_FILESYSTEM_READONLY ","
+					    G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW,
+					    G_PRIORITY_DEFAULT,
+					    state->cancellable, 
+					    query_filesystem_info_callback, 
+					    state);
+	g_object_unref (location);
+}
+
+static void
 extension_info_cancel (NautilusDirectory *directory)
 {
 	if (directory->details->extension_info_in_progress != NULL) {
@@ -4257,6 +4442,7 @@
 	extension_info_stop (directory);
 	mount_stop (directory);
 	thumbnail_stop (directory);
+	filesystem_info_stop (directory);
 
 	doing_io = FALSE;
 	/* Take files that are all done off the queue. */
@@ -4285,6 +4471,7 @@
 		mime_list_start (directory, file, &doing_io);
 		top_left_start (directory, file, &doing_io);
 		thumbnail_start (directory, file, &doing_io);
+		filesystem_info_start (directory, file, &doing_io);
 
 		if (doing_io) {
 			return;
@@ -4356,6 +4543,7 @@
 	extension_info_cancel (directory);
 	thumbnail_cancel (directory);
 	mount_cancel (directory);
+	filesystem_info_cancel (directory);
 
 	/* We aren't waiting for anything any more. */
 	if (waiting_directories != NULL) {
@@ -4435,6 +4623,16 @@
 }
 
 static void
+cancel_filesystem_info_for_file (NautilusDirectory *directory,
+				 NautilusFile      *file)
+{
+	if (directory->details->filesystem_info_state != NULL &&
+	    directory->details->filesystem_info_state->file == file) {
+		filesystem_info_cancel (directory);
+	}
+}
+
+static void
 cancel_link_info_for_file (NautilusDirectory *directory,
 			   NautilusFile      *file)
 {
@@ -4469,6 +4667,9 @@
 	if (request.file_info) {
 		file_info_cancel (directory);
 	}
+	if (request.filesystem_info) {
+		filesystem_info_cancel (directory);
+	}
 	if (request.link_info) {
 		link_info_cancel (directory);
 	}
@@ -4518,6 +4719,9 @@
 	if (request.file_info) {
 		cancel_file_info_for_file (directory, file);
 	}
+	if (request.filesystem_info) {
+		cancel_filesystem_info_for_file (directory, file);
+	}
 	if (request.link_info) {
 		cancel_link_info_for_file (directory, file);
 	}
Index: libnautilus-private/nautilus-file-private.h
===================================================================
--- libnautilus-private/nautilus-file-private.h	(revision 13789)
+++ libnautilus-private/nautilus-file-private.h	(working copy)
@@ -206,6 +206,10 @@
 	eel_boolean_bit can_mount                     : 1;
 	eel_boolean_bit can_unmount                   : 1;
 	eel_boolean_bit can_eject                     : 1;
+
+	eel_boolean_bit filesystem_readonly           : 1;
+	eel_boolean_bit filesystem_use_preview        : 2; /* GFilesystemPreviewType */
+	eel_boolean_bit filesystem_info_is_up_to_date : 1;
 };
 
 typedef struct {
Index: libnautilus-private/nautilus-autorun.c
===================================================================
--- libnautilus-private/nautilus-autorun.c	(revision 13789)
+++ libnautilus-private/nautilus-autorun.c	(working copy)
@@ -1339,10 +1339,12 @@
 			ignore_autorun = TRUE;
 			g_object_set_data (G_OBJECT (enclosing_volume), "nautilus-inhibit-autorun", NULL);
 		}
-		g_object_unref (enclosing_volume);
 	}
 
 	if (ignore_autorun) {
+		if (enclosing_volume != NULL) {
+			g_object_unref (enclosing_volume);
+		}
 		return FALSE;
 	}
 	
@@ -1360,11 +1362,15 @@
 		}
 	}
 
-	if (!g_file_is_native (root)) {
-		/* only do autorun on local files */
-		/* TODO: Maybe we should do this on some gvfs mounts? like gphoto: ? */
-		ignore_autorun = TRUE;
+	/* only do autorun on local files or files where g_volume_should_automount() returns TRUE */
+	ignore_autorun = TRUE;
+	if (g_file_is_native (root) || 
+	    (enclosing_volume != NULL && g_volume_should_automount (enclosing_volume))) {
+		ignore_autorun = FALSE;
 	}
+	if (enclosing_volume != NULL) {
+		g_object_unref (enclosing_volume);
+	}
 	g_object_unref (root);
 
 	return !ignore_autorun;
Index: libnautilus-private/nautilus-directory-private.h
===================================================================
--- libnautilus-private/nautilus-directory-private.h	(revision 13789)
+++ libnautilus-private/nautilus-directory-private.h	(working copy)
@@ -44,6 +44,7 @@
 typedef struct MimeListState MimeListState;
 typedef struct ThumbnailState ThumbnailState;
 typedef struct MountState MountState;
+typedef struct FilesystemInfoState FilesystemInfoState;
 
 struct NautilusDirectoryDetails
 {
@@ -106,6 +107,8 @@
 	ThumbnailState *thumbnail_state;
 
 	MountState *mount_state;
+
+	FilesystemInfoState *filesystem_info_state;
 	
 	TopLeftTextReadState *top_left_read_state;
 
@@ -114,6 +117,8 @@
 	GList *file_operations_in_progress; /* list of FileOperation * */
 
 	GHashTable *hidden_file_hash;
+
+
 };
 
 /* A request for information about one or more files. */
@@ -130,6 +135,7 @@
 	gboolean extension_info;
 	gboolean thumbnail;
 	gboolean mount;
+	gboolean filesystem_info;
 } Request;
 
 NautilusDirectory *nautilus_directory_get_existing                    (GFile                     *location);
Index: libnautilus-private/nautilus-file-attributes.h
===================================================================
--- libnautilus-private/nautilus-file-attributes.h	(revision 13789)
+++ libnautilus-private/nautilus-file-attributes.h	(working copy)
@@ -41,6 +41,7 @@
 	NAUTILUS_FILE_ATTRIBUTE_EXTENSION_INFO = 1 << 8,
 	NAUTILUS_FILE_ATTRIBUTE_THUMBNAIL = 1 << 9,
 	NAUTILUS_FILE_ATTRIBUTE_MOUNT = 1 << 10,
+	NAUTILUS_FILE_ATTRIBUTE_FILESYSTEM_INFO = 1 << 11,
 } NautilusFileAttributes;
 
 #endif /* NAUTILUS_FILE_ATTRIBUTES_H */
Index: libnautilus-private/nautilus-file-operations.c
===================================================================
--- libnautilus-private/nautilus-file-operations.c	(revision 13789)
+++ libnautilus-private/nautilus-file-operations.c	(working copy)
@@ -2040,7 +2040,6 @@
 	GMountOperation *mount_op;
 	
 	mount_op = eel_mount_operation_new (parent_window);
-	nautilus_inhibit_autorun_for_volume (volume);
 	g_volume_mount (volume, 0, mount_op, NULL, volume_mount_cb, mount_op);
 }
 
Index: libnautilus-private/nautilus-file.c
===================================================================
--- libnautilus-private/nautilus-file.c	(revision 13789)
+++ libnautilus-private/nautilus-file.c	(working copy)
@@ -3199,11 +3199,31 @@
         return FALSE;
 }
 
+GFilesystemPreviewType
+nautilus_file_get_filesystem_use_preview (NautilusFile *file)
+{
+	GFilesystemPreviewType use_preview;
+	NautilusFile *parent;
+
+	parent = nautilus_file_get_parent (file);
+	if (parent != NULL) {
+		use_preview = parent->details->filesystem_use_preview;
+		g_object_unref (parent);
+	} else {
+		use_preview = 0;
+	}
+
+	return use_preview;
+}
+
 gboolean
 nautilus_file_should_show_thumbnail (NautilusFile *file)
 {
 	const char *mime_type;
+	GFilesystemPreviewType use_preview;
 
+	use_preview = nautilus_file_get_filesystem_use_preview (file);
+
 	mime_type = eel_ref_str_peek (file->details->mime_type);
 	if (mime_type == NULL) {
 		mime_type = "application/octet-stream";
@@ -3213,14 +3233,26 @@
 	    nautilus_file_get_size (file) > (unsigned int)cached_thumbnail_limit) {
 		return FALSE;
 	}
-	
+
 	if (show_image_thumbs == NAUTILUS_SPEED_TRADEOFF_ALWAYS) {
-		return TRUE;
+		if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) {
+			return FALSE;
+		} else {
+			return TRUE;
+		}
 	} else if (show_image_thumbs == NAUTILUS_SPEED_TRADEOFF_NEVER) {
 		return FALSE;
 	} else {
-		/* only local files */
-		return nautilus_file_is_local (file);
+		if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) {
+			/* file system says to never thumbnail anything */
+			return FALSE;
+		} else if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL) {
+			/* file system says we should treat file as if it's local */
+			return TRUE;
+		} else {
+			/* only local files */
+			return nautilus_file_is_local (file);
+		}
 	}
 
 	return FALSE;
@@ -3715,10 +3747,18 @@
 static gboolean
 get_speed_tradeoff_preference_for_file (NautilusFile *file, NautilusSpeedTradeoffValue value)
 {
+	GFilesystemPreviewType use_preview;
+
 	g_return_val_if_fail (NAUTILUS_IS_FILE (file), FALSE);
+
+	use_preview = nautilus_file_get_filesystem_use_preview (file);
 	
 	if (value == NAUTILUS_SPEED_TRADEOFF_ALWAYS) {
-		return TRUE;
+		if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) {
+			return FALSE;
+		} else {
+			return TRUE;
+		}
 	}
 	
 	if (value == NAUTILUS_SPEED_TRADEOFF_NEVER) {
@@ -3726,7 +3766,17 @@
 	}
 
 	g_assert (value == NAUTILUS_SPEED_TRADEOFF_LOCAL_ONLY);
-	return nautilus_file_is_local (file);
+
+	if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_NEVER) {
+		/* file system says to never preview anything */
+		return FALSE;
+	} else if (use_preview == G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL) {
+		/* file system says we should treat file as if it's local */
+		return TRUE;
+	} else {
+		/* only local files */
+		return nautilus_file_is_local (file);
+	}
 }
 
 gboolean
@@ -5683,10 +5733,39 @@
 GMount *
 nautilus_file_get_mount (NautilusFile *file)
 {
-	if (file->details->mount) {
-		return g_object_ref (file->details->mount);
+	NautilusFile *f, *g;
+	GMount *result;
+
+	/* Try to get the mount all the way up the directory chain.
+	 *
+	 * Note that a GMount may simply not exist for a given
+	 * NautilusFile; this is because we only have GMount objects
+	 * for user visible mounts. 
+	 *
+	 * As an example this function will return NULL for the
+	 * NautilusFile representing the directory /home/user as both
+	 * mounts for / or /home are not considered user-visible
+	 * mounts.
+	 */
+
+	/* TODO: this is not exactly right; the parent folders all the
+	 * way up to the root are not necessarily loaded 
+	 */
+
+	result = NULL;
+	f = nautilus_file_ref (file);
+	while (f != NULL) {
+		if (f->details->mount != NULL) {
+			result = g_object_ref (f->details->mount);
+			nautilus_file_unref (f);
+			break;
+		}
+		g = nautilus_file_get_parent (f);
+		nautilus_file_unref (f);
+		f = g;
 	}
-	return NULL;
+
+	return result;
 }
 
 /**
Index: libnautilus-private/nautilus-file.h
===================================================================
--- libnautilus-private/nautilus-file.h	(revision 13789)
+++ libnautilus-private/nautilus-file.h	(working copy)
@@ -218,6 +218,7 @@
 									 GFileInfo                      *attributes,
 									 NautilusFileOperationCallback   callback,
 									 gpointer                        callback_data);
+GFilesystemPreviewType  nautilus_file_get_filesystem_use_preview        (NautilusFile *file);
 
 /* Permissions. */
 gboolean                nautilus_file_can_get_permissions               (NautilusFile                   *file);
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 1315)
+++ ChangeLog	(working copy)
@@ -1,3 +1,46 @@
+2008-02-21  David Zeuthen  <davidz redhat com>
+
+	* client/gdaemonfile.c: (g_daemon_file_find_enclosing_mount):
+	* client/gdaemonvolumemonitor.c:
+	(g_daemon_volume_monitor_find_mount_by_mount_info),
+	(g_daemon_volume_monitor_init), (g_daemon_volume_monitor_finalize),
+	(is_supported):
+	* client/gdaemonvolumemonitor.h: Prefer to return a GDaemonMount
+	from an existing GDaemonVolumeMonitor rather than rolling our own
+	for GDaemonFile's g_file_find_enclosing_mount()
+	implementation. This is to ensure that g_mount_get_volume() will
+	work properly with mounts that are adopted by GVolume objects from
+	other volume monitors.
+
+	* configure.ac: Check for libgphoto2
+
+	* daemon/Makefile.am:
+	* daemon/gvfsbackendcdda.c: (release_device),
+	(find_udi_for_device), (_hal_device_removed),
+	(g_vfs_backend_cdda_finalize), (do_mount), (do_unmount),
+	(do_query_fs_info), (g_vfs_backend_cdda_class_init): Use HAL to
+	detect when the disc is removed and then forcibly unmount the
+	mount. Report size of disc. Hint the file manager to preview
+	files.
+
+	* hal/ghaldrive.c: (_drive_get_description), (_drive_get_icon):
+	* hal/ghalmount.c: (do_update_from_hal):
+	* hal/ghalvolume.c: (dupv_and_uniqify), (do_update_from_hal),
+	(do_update_from_hal_for_camera), (update_from_hal),
+	(g_hal_volume_new), (g_hal_volume_should_automount):
+	* hal/ghalvolumemonitor.c: (get_hal_pool), (adopt_orphan_mount),
+	(should_drive_be_ignored), (update_cameras):
+	* hal/ghalvolumemonitor.h:
+	* hal/hal-pool.c: (has_cap_only): Ensure that audio and blank CD's
+	are displayed (#514139).  Read info.desktop.[icon|name] properties
+	from hal and use these if found. Use proper icon for audio players
+	and make the gphoto2 detection support it as well. Also check for
+	subsystem when filtering on hal capabilities.
+
+	* daemon/gphoto2.mount.in:
+	* daemon/gvfsbackendgphoto2.c:
+	Land the gphoto2 backend.
+
 2008-02-20  Alexander Larsson  <alexl redhat com>
 
         * daemon/Makefile.am:
Index: configure.ac
===================================================================
--- configure.ac	(revision 1315)
+++ configure.ac	(working copy)
@@ -222,6 +222,43 @@
 
 AM_CONDITIONAL(USE_HAL, [test "$msg_hal" = "yes"])
 
+dnl *************************
+dnl *** Check for gphoto2 ***
+dnl *************************
+AC_ARG_ENABLE(hal, [  --disable-gphoto2           build without gphoto2 support])
+msg_gphoto2=no
+GPHOTO2_LIBS=
+GPHOTO2_CFLAGS=
+
+if test "x$enable_gphoto2" != "xno"; then
+  PKG_CHECK_EXISTS(libgphoto2, msg_gphoto2=yes)
+
+  # Need OS tweaks in hal volume monitor backend
+  case "$host" in
+    *-linux*)
+      use_gphoto2=yes
+      ;;
+    *)
+      use_gphoto2=no
+      ;;
+  esac
+
+  if test "x$msg_gphoto2" == "xyes"; then
+    if test "x$use_gphoto2" == "xyes"; then
+      PKG_CHECK_MODULES(GPHOTO2, gphoto2)
+      AC_DEFINE(HAVE_GPHOTO2, 1, [Define to 1 if gphoto2 is available])
+    else
+      AC_MSG_WARN([Not building with gphoto2 support. Need OS tweaks in hal volume monitor.])
+      msg_gphoto2=no
+    fi
+  fi
+fi
+
+AC_SUBST(GPHOTO2_LIBS)
+AC_SUBST(GPHOTO2_CFLAGS)
+
+AM_CONDITIONAL(USE_GPHOTO2, [test "$msg_gphoto2" = "yes"])
+
 dnl *******************************
 dnl *** Check for GNOME Keyring ***
 dnl *******************************
@@ -386,6 +423,7 @@
 	Samba support:	              $msg_samba
 	FUSE support:                 $msg_fuse
         CDDA support:                 $msg_cdda
+        Gphoto2 support:              $msg_gphoto2
         GConf support:                $msg_gconf
 	Use HAL for volume monitor:   $msg_hal (with fast init path: $have_hal_fast_init)
 	GNOME Keyring support:        $msg_keyring
Index: daemon/gvfsbackendcdda.c
===================================================================
--- daemon/gvfsbackendcdda.c	(revision 1315)
+++ daemon/gvfsbackendcdda.c	(working copy)
@@ -34,10 +34,13 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <string.h>
+#include <stdlib.h>
 
 #include <glib/gstdio.h>
 #include <glib/gi18n.h>
 #include <gio/gio.h>
+#include <libhal.h>
+#include <dbus/dbus.h>
 
 #include "gvfsbackendcdda.h"
 #include "gvfsjobopenforread.h"
@@ -52,9 +55,6 @@
 /* TODO:
  *
  * - GVFS integration
- *   - Need to unmount ourselves when the backing media is removed
- *   - Need to have some way of making our resulting GDaemonMount object (in the client address space)
- *     be associated with the GVolume (probably stems from the HAL volume monitor).. both ways
  *   - g_vfs_backend_set_display_name() needs to work post mount
  *
  * - Metadata
@@ -86,6 +86,12 @@
 {
   GVfsBackend parent_instance;
 
+  DBusConnection *dbus_connection;
+  LibHalContext *hal_ctx;
+  char *hal_udi;
+
+  guint64 size;
+
   char *device_path;
   cdrom_drive_t *drive;
   int num_open_files;
@@ -94,13 +100,80 @@
 G_DEFINE_TYPE (GVfsBackendCdda, g_vfs_backend_cdda, G_VFS_TYPE_BACKEND)
 
 static void
+release_device (GVfsBackendCdda *cdda_backend)
+{
+  g_free (cdda_backend->device_path);
+  cdda_backend->device_path = NULL;
+
+  if (cdda_backend->drive != NULL)
+    {
+      cdio_cddap_close (cdda_backend->drive);
+      cdda_backend->drive = NULL;
+    }
+}
+
+static void
+find_udi_for_device (GVfsBackendCdda *cdda_backend)
+{
+  int num_devices;
+  char **devices;
+  int n;
+
+  cdda_backend->hal_udi = NULL;
+
+  devices = libhal_manager_find_device_string_match (cdda_backend->hal_ctx,
+                                                     "block.device",
+                                                     cdda_backend->device_path,
+                                                     &num_devices,
+                                                     NULL);
+  if (devices != NULL)
+    {
+      for (n = 0; n < num_devices && cdda_backend->hal_udi == NULL; n++)
+        {
+          char *udi = devices[n];
+          LibHalPropertySet *ps;
+
+          ps = libhal_device_get_all_properties (cdda_backend->hal_ctx, udi, NULL);
+          if (ps != NULL)
+            {
+              if (libhal_ps_get_bool (ps, "block.is_volume"))
+                {
+                  cdda_backend->hal_udi = g_strdup (udi);
+                  cdda_backend->size = libhal_ps_get_uint64 (ps, "volume.size");
+                }                
+            }
+                  
+          libhal_free_property_set (ps);
+        }
+    }
+  libhal_free_string_array (devices);
+
+  /*g_warning ("found udi '%s'", cdda_backend->hal_udi);*/
+}
+
+static void
+_hal_device_removed (LibHalContext *hal_ctx, const char *udi)
+{
+  GVfsBackendCdda *cdda_backend;
+
+  cdda_backend = G_VFS_BACKEND_CDDA (libhal_ctx_get_user_data (hal_ctx));
+
+  if (cdda_backend->hal_udi != NULL && strcmp (udi, cdda_backend->hal_udi) == 0)
+    {
+      /*g_warning ("we have been removed!");*/
+      /* TODO: need a cleaner way to force unmount ourselves */
+      exit (1);
+    }
+}
+
+static void
 g_vfs_backend_cdda_finalize (GObject *object)
 {
   GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (object);
 
   //g_warning ("finalizing %p", object);
 
-  g_free (cdda_backend->device_path);
+  release_device (cdda_backend);
 
   if (G_OBJECT_CLASS (g_vfs_backend_cdda_parent_class)->finalize)
     (*G_OBJECT_CLASS (g_vfs_backend_cdda_parent_class)->finalize) (object);
@@ -135,9 +208,52 @@
   GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend);
   GError *error = NULL;
   GMountSpec *cdda_mount_spec;
+  DBusError dbus_error;
 
   //g_warning ("do_mount %p", cdda_backend);
 
+  /* setup libhal */
+
+  dbus_error_init (&dbus_error);
+  cdda_backend->dbus_connection = dbus_bus_get_private (DBUS_BUS_SYSTEM, &dbus_error);
+  if (dbus_error_is_set (&dbus_error))
+    {
+      release_device (cdda_backend);
+      dbus_error_free (&dbus_error);
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot connect to the system bus"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+  
+  cdda_backend->hal_ctx = libhal_ctx_new ();
+  if (cdda_backend->hal_ctx == NULL)
+    {
+      release_device (cdda_backend);
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot create libhal context"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  _g_dbus_connection_integrate_with_main (cdda_backend->dbus_connection);
+  libhal_ctx_set_dbus_connection (cdda_backend->hal_ctx, cdda_backend->dbus_connection);
+  
+  if (!libhal_ctx_init (cdda_backend->hal_ctx, &dbus_error))
+    {
+      release_device (cdda_backend);
+      dbus_error_free (&dbus_error);
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot initialize libhal"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  libhal_ctx_set_device_removed (cdda_backend->hal_ctx, _hal_device_removed);
+  libhal_ctx_set_user_data (cdda_backend->hal_ctx, cdda_backend);
+
+  /* setup libcdio */
+
   host = g_mount_spec_get (mount_spec, "host");
   //g_warning ("host=%s", host);
   if (host == NULL)
@@ -145,11 +261,14 @@
       g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "%s", _("No drive specified"));
       g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
       g_error_free (error);
+      release_device (cdda_backend);
       return;
     }
 
   cdda_backend->device_path = g_strdup_printf ("/dev/%s", host);
 
+  find_udi_for_device (cdda_backend);
+
   cdda_backend->drive = cdio_cddap_identify (cdda_backend->device_path, 0, NULL);
   if (cdda_backend->drive == NULL)
     {
@@ -157,6 +276,7 @@
                    _("Cannot find drive %s"), cdda_backend->device_path);
       g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
       g_error_free (error);
+      release_device (cdda_backend);
       return;
     }
 
@@ -167,11 +287,12 @@
                    _("Drive %s does not contain audio files"), cdda_backend->device_path);
       g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
       g_error_free (error);
+      release_device (cdda_backend);
       return;
     }
 
   /* Translator: %s is the device the disc is inserted into */
-  fuse_name = g_strdup_printf (_("Audio Disc on %s"), host);
+  fuse_name = g_strdup_printf (_("cdda mount on %s"), host);
   display_name = g_strdup_printf (_("Audio Disc"));
   g_vfs_backend_set_stable_name (backend, fuse_name);
   g_vfs_backend_set_display_name (backend, display_name);
@@ -241,14 +362,11 @@
       return;
     }
 
-  if (cdda_backend->drive != NULL)
-    {
-      //g_warning ("closed drive %p", backend);
-      cdio_cddap_close (cdda_backend->drive);
-    }
+  release_device (cdda_backend);
   
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
   //g_warning ("unmounted %p", backend);
-  g_vfs_job_succeeded (G_VFS_JOB (job));
 }
 
 /* returns -1 if we couldn't map */
@@ -766,6 +884,33 @@
 }
 
 static void
+do_query_fs_info (GVfsBackend *backend,
+		  GVfsJobQueryFsInfo *job,
+		  const char *filename,
+		  GFileInfo *info,
+		  GFileAttributeMatcher *attribute_matcher)
+{
+  GVfsBackendCdda *cdda_backend = G_VFS_BACKEND_CDDA (backend);
+
+  g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "cdda");
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL);
+
+  if (cdda_backend->size > 0)
+    {
+      g_file_info_set_attribute_uint64 (info, 
+                                        G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, 
+                                        cdda_backend->size);
+    }
+  g_file_info_set_attribute_uint64 (info, 
+                                    G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 
+                                    0);
+  
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+
+static void
 g_vfs_backend_cdda_class_init (GVfsBackendCddaClass *klass)
 {
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
@@ -781,5 +926,6 @@
   backend_class->seek_on_read = do_seek_on_read;
   backend_class->close_read = do_close_read;
   backend_class->query_info = do_query_info;
+  backend_class->query_fs_info = do_query_fs_info;
   backend_class->enumerate = do_enumerate;
 }
Index: daemon/Makefile.am
===================================================================
--- daemon/Makefile.am	(revision 1315)
+++ daemon/Makefile.am	(working copy)
@@ -57,6 +57,12 @@
 libexec_PROGRAMS += gvfsd-cdda
 endif
 
+mount_in_files += gphoto2.mount.in
+if USE_GPHOTO2
+mount_DATA += gphoto2.mount
+libexec_PROGRAMS += gvfsd-gphoto2
+endif
+
 mount_in_files += network.mount.in
 if USE_GCONF
 mount_DATA += network.mount
@@ -262,11 +268,25 @@
 	-DBACKEND_HEADER=gvfsbackendcdda.h \
 	-DDEFAULT_BACKEND_TYPE=cdda \
 	-DMAX_JOB_THREADS=1 \
-	$(CDDA_CFLAGS) \
+	$(CDDA_CFLAGS) $(HAL_CFLAGS) \
 	-DBACKEND_TYPES='"cdda", G_VFS_TYPE_BACKEND_CDDA,'
 
-gvfsd_cdda_LDADD = $(libraries) $(CDDA_LIBS)
+gvfsd_cdda_LDADD = $(libraries) $(CDDA_LIBS) $(HAL_LIBS)
 
+gvfsd_gphoto2_SOURCES = \
+	gvfsbackendgphoto2.c gvfsbackendgphoto2.h \
+	daemon-main.c daemon-main.h \
+	daemon-main-generic.c 
+
+gvfsd_gphoto2_CPPFLAGS = \
+	-DBACKEND_HEADER=gvfsbackendgphoto2.h \
+	-DDEFAULT_BACKEND_TYPE=gphoto2 \
+	-DMAX_JOB_THREADS=1 \
+	$(GPHOTO2_CFLAGS) $(HAL_CFLAGS) \
+	-DBACKEND_TYPES='"gphoto2", G_VFS_TYPE_BACKEND_GPHOTO2,'
+
+gvfsd_gphoto2_LDADD = $(libraries) $(GPHOTO2_LIBS) $(HAL_LIBS)
+
 gvfsd_http_SOURCES = \
 	soup-input-stream.c soup-input-stream.h \
 	soup-output-stream.c soup-output-stream.h \
Index: hal/ghalmount.c
===================================================================
--- hal/ghalmount.c	(revision 1315)
+++ hal/ghalmount.c	(working copy)
@@ -366,10 +366,8 @@
 {
   HalDevice *volume;
   HalDevice *drive;
-
   char *name;
   const char *icon_name;
-
   const char *drive_type;
   const char *drive_bus;
   gboolean drive_uses_removable_media;
@@ -380,6 +378,11 @@
   gboolean volume_disc_has_data;
   const char *volume_disc_type;
   gboolean volume_disc_is_blank;
+  gboolean is_audio_player;
+  const char *icon_from_hal;
+  const char *volume_icon_from_hal;
+  const char *name_from_hal;
+  const char *volume_name_from_hal;
 
   volume = m->device;
   drive = m->drive_device;
@@ -394,13 +397,30 @@
   volume_disc_has_data = hal_device_get_property_bool (volume, "volume.disc.has_data");
   volume_disc_is_blank = hal_device_get_property_bool (volume, "volume.disc.is_blank");
   volume_disc_type = hal_device_get_property_string (volume, "volume.disc.type");
-  
+  is_audio_player = hal_device_has_capability (drive, "portable_audio_player");
+  icon_from_hal = hal_device_get_property_string (drive, "info.desktop.icon");
+  volume_icon_from_hal = hal_device_get_property_string (volume, "info.desktop.icon");
+  name_from_hal = hal_device_get_property_string (drive, "info.desktop.name");
+  volume_name_from_hal = hal_device_get_property_string (volume, "info.desktop.name");
+
   /*g_warning ("drive_type='%s'", drive_type); */
   /*g_warning ("drive_bus='%s'", drive_bus); */
   /*g_warning ("drive_uses_removable_media=%d", drive_uses_removable_media); */
-  
-  if (strcmp (drive_type, "disk") == 0)
+
+  if (strlen (volume_icon_from_hal) > 0)
     {
+      icon_name = volume_icon_from_hal;
+    }
+  else if (strlen (icon_from_hal) > 0)
+    {
+      icon_name = icon_from_hal;
+    }
+  else if (is_audio_player)
+    {
+      icon_name = "multimedia-player";
+    }
+  else if (strcmp (drive_type, "disk") == 0)
+    {
       if (strcmp (drive_bus, "ide") == 0)
         icon_name = "drive-harddisk-ata";
       else if (strcmp (drive_bus, "scsi") == 0)
@@ -430,7 +450,15 @@
     icon_name = "drive-harddisk";
 
   
-  if (volume_fs_label != NULL && strlen (volume_fs_label) > 0)
+  if (strlen (volume_name_from_hal) > 0)
+    {
+      name = g_strdup (volume_name_from_hal);
+    }
+  else if (strlen (name_from_hal) > 0)
+    {
+      name = g_strdup (name_from_hal);
+    }
+  else if (volume_fs_label != NULL && strlen (volume_fs_label) > 0)
     name = g_strdup (volume_fs_label);
   else if (volume_is_disc)
     {
Index: hal/ghalvolume.c
===================================================================
--- hal/ghalvolume.c	(revision 1315)
+++ hal/ghalvolume.c	(working copy)
@@ -202,6 +202,36 @@
   return str;
 }
 
+static char **
+dupv_and_uniqify (char **str_array)
+{
+  int n, m, o;
+  int len;
+  char **result;
+
+  result = g_strdupv (str_array);
+  len = g_strv_length (result);
+
+  for (n = 0; n < len; n++)
+    {
+      char *s = result[n];
+      for (m = n + 1; m < len; m++)
+        {
+          char *p = result[m];
+          if (strcmp (s, p) == 0)
+            {
+              for (o = m + 1; o < len; o++)
+                result[o - 1] = result[o];
+              len--;
+              result[len] = NULL;
+              m--;
+            }
+        }
+    }
+
+  return result;
+}
+
 static void
 do_update_from_hal (GHalVolume *mv)
 {
@@ -285,7 +315,7 @@
 
   g_object_set_data_full (G_OBJECT (mv), 
                           "hal-storage-device-capabilities",
-                          g_strdupv (hal_device_get_property_strlist (mv->drive_device, "info.capabilities")),
+                          dupv_and_uniqify (hal_device_get_property_strlist (mv->drive_device, "info.capabilities")),
                           (GDestroyNotify) g_strfreev);
 
   if (volume_disc_type != NULL && strlen (volume_disc_type) == 0)
@@ -296,33 +326,81 @@
                           (GDestroyNotify) g_free);
 }
 
-#ifdef _WITH_GPHOTO2
+#ifdef HAVE_GPHOTO2
 static void
 do_update_from_hal_for_camera (GHalVolume *v)
 {
   const char *vendor;
   const char *product;
+  const char *icon_from_hal;
+  const char *name_from_hal;
+  gboolean is_audio_player;
 
   vendor = hal_device_get_property_string (v->drive_device, "usb_device.vendor");
   product = hal_device_get_property_string (v->drive_device, "usb_device.product");
+  icon_from_hal = hal_device_get_property_string (v->device, "info.desktop.icon");
+  name_from_hal = hal_device_get_property_string (v->device, "info.desktop.name");
 
-  if (vendor == NULL)
+  is_audio_player = hal_device_has_capability (v->device, "portable_audio_player");
+
+  v->name = NULL;
+  if (strlen (name_from_hal) > 0)
     {
+      v->name = g_strdup (name_from_hal);
+    }
+  else if (vendor == NULL)
+    {
       if (product != NULL)
         v->name = g_strdup (product);
-      else
-        v->name = g_strdup (_("Camera"));
     }
   else
     {
       if (product != NULL)
-        v->name = g_strdup_printf ("%s %s", vendor, product);
+        {
+          v->name = g_strdup_printf ("%s %s", vendor, product);
+        }
       else
-        v->name = g_strdup_printf (_("%s Camera"), vendor);
+        {
+          if (is_audio_player)
+            {
+              v->name = g_strdup_printf (_("%s Audio Player"), vendor);
+            }
+          else
+            {
+              v->name = g_strdup_printf (_("%s Camera"), vendor);
+            }
+        }
     }
+  if (v->name == NULL)
+    {
+      if (is_audio_player)
+        {
+          v->name = g_strdup (_("Audio Player"));
+        }
+      else
+        {
+          v->name = g_strdup (_("Camera"));
+        }
+    }
 
-  v->icon = g_strdup ("camera");
+  if (strlen (icon_from_hal) > 0)
+    {
+      v->icon = g_strdup (icon_from_hal);
+    }
+  else if (is_audio_player)
+    {
+      v->icon = g_strdup ("multimedia-player");
+    }
+  else
+    {
+      v->icon = g_strdup ("camera");
+    }
   v->mount_path = NULL;
+
+  g_object_set_data_full (G_OBJECT (v), 
+                          "hal-storage-device-capabilities",
+                          dupv_and_uniqify (hal_device_get_property_strlist (v->device, "info.capabilities")),
+                          (GDestroyNotify) g_strfreev);
 }
 #endif
 
@@ -340,8 +418,10 @@
   g_free (mv->name);
   g_free (mv->icon);
   g_free (mv->mount_path);
-#ifdef _WITH_GPHOTO2
-  if (hal_device_has_capability (mv->device, "camera"))
+#ifdef HAVE_GPHOTO2
+  if (hal_device_has_capability (mv->device, "camera") || 
+      (hal_device_has_capability (mv->device, "portable_audio_player") &&
+       hal_device_get_property_bool (mv->device, "camera.libgphoto2.support")))
     do_update_from_hal_for_camera (mv);
   else
     do_update_from_hal (mv);
@@ -432,9 +512,12 @@
       
       device_path = hal_device_get_property_string (device, "block.device");
     }
-#ifdef _WITH_GPHOTO2
-  else if (hal_device_has_capability (device, "camera"))
+#ifdef HAVE_GPHOTO2
+  else if (hal_device_has_capability (device, "camera") ||
+           (hal_device_has_capability (device, "portable_audio_player") &&
+            hal_device_get_property_bool (device, "camera.libgphoto2.support")))
     {
+
       /* OK, so we abuse storage_udi and drive_device for the USB main
        * device that holds this interface... 
        */
@@ -609,10 +692,8 @@
 static gboolean
 g_hal_volume_should_automount (GVolume *volume)
 {
-  GHalVolume *hal_volume = G_HAL_VOLUME (volume);
-  /* TODO: For now, just never automount things that are not
-     local. Need to figure out a better approach later. */
-  return hal_volume->foreign_mount == NULL;
+  /* all of the volumes we manage should be automounted */
+  return TRUE;
 }
 
 static GDrive *
Index: hal/ghaldrive.c
===================================================================
--- hal/ghaldrive.c	(revision 1315)
+++ hal/ghaldrive.c	(working copy)
@@ -123,12 +123,19 @@
   char *s = NULL;
   const char *drive_type;
   const char *drive_bus;
+  const char *name_from_hal;
 
   drive_type = hal_device_get_property_string (d, "storage.drive_type");
   drive_bus = hal_device_get_property_string (d, "storage.bus");
+  name_from_hal = hal_device_get_property_string (d, "info.desktop.name");
 
-  if (strcmp (drive_type, "cdrom") == 0)
+  
+  if (strlen (name_from_hal) > 0)
     {
+      s = g_strdup (name_from_hal);
+    }
+  else if (strcmp (drive_type, "cdrom") == 0)
+    {
       const char *first;
       const char *second;
       
@@ -230,22 +237,34 @@
   char *s = NULL;
   const char *drive_type;
   const char *drive_bus;
+  const char *icon_from_hal;
+  gboolean is_audio_player;
 
   drive_type = hal_device_get_property_string (d, "storage.drive_type");
   drive_bus = hal_device_get_property_string (d, "storage.bus");
-  
-  if (strcmp (drive_type, "disk") == 0) {
-    if (strcmp (drive_bus, "ide") == 0)
-      s = g_strdup ("drive-removable-media-ata");
-    else if (strcmp (drive_bus, "scsi") == 0)
-      s = g_strdup ("drive-removable-media-scsi");
-    else if (strcmp (drive_bus, "ieee1394") == 0)
-      s = g_strdup ("drive-removable-media-ieee1394");
-    else if (strcmp (drive_bus, "usb") == 0)
-      s = g_strdup ("drive-removable-media-usb");
-    else
-      s = g_strdup ("drive-removable-media");
-  }
+  is_audio_player = hal_device_has_capability (d, "portable_audio_player");
+  icon_from_hal = hal_device_get_property_string (d, "info.desktop.icon");
+
+  if (strlen (icon_from_hal) > 0)
+    {
+      s = g_strdup (icon_from_hal);
+    }
+  else if (is_audio_player)
+    {
+      s = g_strdup ("multimedia-player");
+  } else if (strcmp (drive_type, "disk") == 0)
+    {
+      if (strcmp (drive_bus, "ide") == 0)
+        s = g_strdup ("drive-removable-media-ata");
+      else if (strcmp (drive_bus, "scsi") == 0)
+        s = g_strdup ("drive-removable-media-scsi");
+      else if (strcmp (drive_bus, "ieee1394") == 0)
+        s = g_strdup ("drive-removable-media-ieee1394");
+      else if (strcmp (drive_bus, "usb") == 0)
+        s = g_strdup ("drive-removable-media-usb");
+      else
+        s = g_strdup ("drive-removable-media");
+    }
   else if (strcmp (drive_type, "cdrom") == 0)
     {
       /* TODO: maybe there's a better heuristic than this */
Index: hal/hal-pool.c
===================================================================
--- hal/hal-pool.c	(revision 1315)
+++ hal/hal-pool.c	(working copy)
@@ -137,12 +137,18 @@
 static gboolean
 has_cap_only (HalPool *pool, HalDevice *device)
 {
+  const char *subsys;
   unsigned int n;
 
   for (n = 0; pool->priv->cap_only != NULL && pool->priv->cap_only[n] != NULL; n++)
     {
       if (hal_device_has_capability (device, pool->priv->cap_only[n]))
         return TRUE;
+
+      subsys = hal_device_get_property_string (device, "info.subsystem");
+
+      if (subsys != NULL && strcmp (subsys, pool->priv->cap_only[n]) == 0)
+        return TRUE;
     }
   
   return FALSE;
Index: hal/ghalvolumemonitor.c
===================================================================
--- hal/ghalvolumemonitor.c	(revision 1315)
+++ hal/ghalvolumemonitor.c	(working copy)
@@ -93,7 +93,7 @@
 static HalPool *
 get_hal_pool (void)
 {
-  char *cap_only[] = {"block", "camera", NULL};
+  char *cap_only[] = {"block", "camera", "portable_audio_player", "usb_device", NULL};
 
   if (pool == NULL)
     pool = hal_pool_new (cap_only);
@@ -433,7 +433,7 @@
   ret = NULL;
   mount_root = g_mount_get_root (mount);
 
-  /* right now we only support discs as foreign mounts */
+  /* cdda:// as foreign mounts */
   for (l = the_volume_monitor->disc_volumes; l != NULL; l = l->next)
     {
       GHalVolume *volume = l->data;
@@ -442,9 +442,24 @@
         {
           g_hal_volume_adopt_foreign_mount (volume, mount);
           ret = g_object_ref (volume);
-          break;
+          goto found;
         }
     }
+
+  /* gphoto2:// as foreign mounts */
+  for (l = the_volume_monitor->camera_volumes; l != NULL; l = l->next)
+    {
+      GHalVolume *volume = l->data;
+      
+      if (g_hal_volume_has_foreign_mount_root (volume, mount_root))
+        {
+          g_hal_volume_adopt_foreign_mount (volume, mount);
+          ret = g_object_ref (volume);
+          goto found;
+        }
+    }
+
+ found:
   
   g_object_unref (mount_root);
   return ret;
@@ -647,7 +662,7 @@
   return NULL;
 }
 
-#ifdef _WITH_GPHOTO2
+#ifdef HAVE_GPHOTO2
 static GHalVolume *
 find_camera_volume_by_udi (GHalVolumeMonitor *monitor, const char *udi)
 {
@@ -724,8 +739,12 @@
   const char *drive_udi;
   gboolean all_volumes_ignored, got_volumes;
 
+  /* never ignore drives with removable media */
+  if (hal_device_get_property_bool (d, "storage.removable"))
+    return FALSE;
+
   drive_udi = hal_device_get_udi (d);
-  
+
   volumes = hal_pool_find_by_capability (pool, "volume");
 
   all_volumes_ignored = TRUE;
@@ -736,7 +755,9 @@
       if (strcmp (drive_udi, hal_device_get_property_string (volume_dev, "block.storage_device")) == 0)
         {
           got_volumes = TRUE;
-          if (!should_volume_be_ignored (pool, volume_dev))
+          if (!should_volume_be_ignored (pool, volume_dev) ||
+              hal_device_get_property_bool (volume_dev, "volume.disc.has_audio") ||
+              hal_device_get_property_bool (volume_dev, "volume.disc.is_blank"))
             {
               all_volumes_ignored = FALSE;
               break;
@@ -1096,14 +1117,29 @@
 static void
 update_cameras (GHalVolumeMonitor *monitor)
 {
-#ifdef _WITH_GPHOTO2
+#ifdef HAVE_GPHOTO2
   GList *new_camera_devices;
+  GList *new_mpt_devices;
   GList *removed, *added;
   GList *l, *ll;
   GHalVolume *volume;
   const char *udi;
 
+  new_mpt_devices = hal_pool_find_by_capability (monitor->pool, "portable_audio_player");
+  for (l = new_mpt_devices; l != NULL; l = ll)
+    {
+      HalDevice *d = l->data;
+      ll = l->next;
+      if (! hal_device_get_property_bool (d, "camera.libgphoto2.support"))
+        {
+          /*g_warning ("ignoring %s", hal_device_get_udi (d));*/
+          /* filter out everything that isn't supported by libgphoto2 */
+          new_mpt_devices = g_list_delete_link (new_mpt_devices, l);
+        }
+    }
+
   new_camera_devices = hal_pool_find_by_capability (monitor->pool, "camera");
+  new_camera_devices = g_list_concat (new_camera_devices, new_mpt_devices);
   for (l = new_camera_devices; l != NULL; l = ll)
     {
       HalDevice *d = l->data;
@@ -1152,7 +1188,7 @@
       usb_bus_num = hal_device_get_property_int (d, "usb.bus_number");
       usb_device_num = hal_device_get_property_int (d, "usb.linux.device_number");
 
-      uri = g_strdup_printf ("gphoto2://usb:%03d,%03d", usb_bus_num, usb_device_num);
+      uri = g_strdup_printf ("gphoto2://[usb:%03d,%03d]", usb_bus_num, usb_device_num);
       /*g_warning ("uri is '%s'", uri);*/
       foreign_mount_root = g_file_new_for_uri (uri);
       g_free (uri);
Index: hal/ghalvolumemonitor.h
===================================================================
--- hal/ghalvolumemonitor.h	(revision 1315)
+++ hal/ghalvolumemonitor.h	(working copy)
@@ -29,11 +29,6 @@
 #include <gio/gio.h>
 #include <gio/gunixmounts.h>
 
-/* TODO: need to use different properties on HAL for other OS's (!) */
-#ifdef __linux__
-#define _WITH_GPHOTO2
-#endif
-
 G_BEGIN_DECLS
 
 #define G_TYPE_HAL_VOLUME_MONITOR        (g_hal_volume_monitor_get_type ())
Index: client/gdaemonfile.c
===================================================================
--- client/gdaemonfile.c	(revision 1315)
+++ client/gdaemonfile.c	(working copy)
@@ -1530,7 +1530,12 @@
 
   if (mount_info->user_visible)
     {
-      mount = g_daemon_mount_new (mount_info, NULL);
+      /* if we have a daemon volume monitor then return one of it's mounts */
+      mount = g_daemon_volume_monitor_find_mount_by_mount_info (mount_info);
+      if (mount == NULL)
+        {
+          mount = g_daemon_mount_new (mount_info, NULL);
+        }
       g_mount_info_unref (mount_info);
       
       if (mount)
Index: client/gdaemonvolumemonitor.c
===================================================================
--- client/gdaemonvolumemonitor.c	(revision 1315)
+++ client/gdaemonvolumemonitor.c	(working copy)
@@ -31,6 +31,8 @@
 #include "gdaemonvfs.h"
 #include "gmounttracker.h"
 
+static GDaemonVolumeMonitor *_the_daemon_volume_monitor;
+
 struct _GDaemonVolumeMonitor {
   GVolumeMonitor parent;
 
@@ -103,6 +105,27 @@
   return found_mount;
 }
 
+GDaemonMount *
+g_daemon_volume_monitor_find_mount_by_mount_info (GMountInfo *mount_info)
+{
+  GDaemonMount *daemon_mount;
+
+  if (_the_daemon_volume_monitor == NULL)
+    {
+      return NULL;
+    }
+
+  daemon_mount = find_mount_by_mount_info (_the_daemon_volume_monitor, mount_info);
+  if (daemon_mount != NULL)
+    {
+      return g_object_ref (daemon_mount);
+    }
+  else
+    {
+      return NULL;
+    }
+}
+
 static void
 mount_added (GDaemonVolumeMonitor *daemon_monitor, GMountInfo *mount_info)
 {
@@ -154,6 +177,8 @@
   GMountInfo *info;
   GVolume *volume;
 
+  _the_daemon_volume_monitor = daemon_monitor;
+
   daemon_monitor->mount_tracker = g_mount_tracker_new (_g_daemon_vfs_get_async_bus ());
 
   g_signal_connect_swapped (daemon_monitor->mount_tracker, "mounted",
@@ -198,6 +223,8 @@
   
   if (G_OBJECT_CLASS (g_daemon_volume_monitor_parent_class)->finalize)
     (*G_OBJECT_CLASS (g_daemon_volume_monitor_parent_class)->finalize) (object);
+
+  _the_daemon_volume_monitor = NULL;
 }
 
 static void
@@ -211,7 +238,6 @@
   GVfs *vfs;
   gboolean res;
 
-
   res = FALSE;
 
   /* Don't do anything if the default vfs is not DAEMON_VFS */
Index: client/gdaemonvolumemonitor.h
===================================================================
--- client/gdaemonvolumemonitor.h	(revision 1315)
+++ client/gdaemonvolumemonitor.h	(working copy)
@@ -25,6 +25,7 @@
 
 #include <glib-object.h>
 #include <gio/gio.h>
+#include "gmounttracker.h"
 
 G_BEGIN_DECLS
 
@@ -52,6 +53,8 @@
 
 GVolumeMonitor *g_daemon_volume_monitor_new (void);
 
+GDaemonMount *g_daemon_volume_monitor_find_mount_by_mount_info (GMountInfo *mount_info);
+
 G_END_DECLS
 
 #endif /* __G_DAEMON_VOLUME_MONITOR_H__ */
--- /dev/null	2008-02-20 19:53:08.874023641 -0500
+++ daemon/gphoto2.mount.in	2008-01-18 12:40:00.000000000 -0500
@@ -0,0 +1,4 @@
+[Mount]
+Type=gphoto2
+Exec= libexecdir@/gvfsd-gphoto2
+AutoMount=false
--- /dev/null	2008-02-20 19:53:08.874023641 -0500
+++ daemon/gvfsbackendgphoto2.h	2008-01-18 11:59:25.000000000 -0500
@@ -0,0 +1,51 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __G_VFS_BACKEND_GPHOTO2_H__
+#define __G_VFS_BACKEND_GPHOTO2_H__
+
+#include <gvfsbackend.h>
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_BACKEND_GPHOTO2         (g_vfs_backend_gphoto2_get_type ())
+#define G_VFS_BACKEND_GPHOTO2(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), G_VFS_TYPE_BACKEND_GPHOTO2, GVfsBackendGphoto2))
+#define G_VFS_BACKEND_GPHOTO2_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), G_VFS_TYPE_BACKEND_GPHOTO2, GVfsBackendGphoto2Class))
+#define G_VFS_IS_BACKEND_GPHOTO2(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), G_VFS_TYPE_BACKEND_GPHOTO2))
+#define G_VFS_IS_BACKEND_GPHOTO2_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), G_VFS_TYPE_BACKEND_GPHOTO2))
+#define G_VFS_BACKEND_GPHOTO2_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), G_VFS_TYPE_BACKEND_GPHOTO2, GVfsBackendGphoto2Class))
+
+typedef struct _GVfsBackendGphoto2        GVfsBackendGphoto2;
+typedef struct _GVfsBackendGphoto2Class   GVfsBackendGphoto2Class;
+
+struct _GVfsBackendGphoto2Class
+{
+  GVfsBackendClass parent_class;
+};
+
+GType g_vfs_backend_gphoto2_get_type (void) G_GNUC_CONST;
+  
+GVfsBackendGphoto2 *g_vfs_backend_gphoto2_new (void);
+
+G_END_DECLS
+
+#endif /* __G_VFS_BACKEND_GPHOTO2_H__ */
--- /dev/null	2008-02-20 19:53:08.874023641 -0500
+++ daemon/gvfsbackendgphoto2.c	2008-02-21 02:01:11.000000000 -0500
@@ -0,0 +1,1405 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* GVFS gphoto2 file system driver
+ * 
+ * Copyright (C) 2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+/* NOTE: since we link the libcdio libs (GPLv2) into our process space
+ * the combined work is GPLv2. This source file, however, is LGPLv2+.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib/gstdio.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gphoto2.h>
+#include <libhal.h>
+#include <dbus/dbus.h>
+
+#include "gvfsbackendgphoto2.h"
+#include "gvfsjobopenforread.h"
+#include "gvfsjobread.h"
+#include "gvfsjobseekread.h"
+#include "gvfsjobqueryinfo.h"
+#include "gvfsjobenumerate.h"
+
+/*--------------------------------------------------------------------------------------------------------------*/
+
+/* TODO:
+ *
+ *  - write support
+ *    - be CAREFUL when adding this; need to invalidate the caches below
+ *    - also need to check CameraStorageAccessType in CameraStorageInformation for
+ *      whether the device supports it
+ *
+ *  - support for multiple storage heads
+ *    - need a device that supports this
+ *    - should be different mounts so need to infect GHalVolumeMonitor with libgphoto2
+ *    - probably not a huge priority to add
+ *    - might help properly resolve the hack we're doing in ensure_ignore_prefix()
+ *
+ *  - add payload cache
+ *    - to help alleviate the fact that libgphoto2 doesn't allow partial downloads :-/
+ *    - use max 25% of physical memory or at least 40MB
+ *    - max file size 10% of cache or at least 20MB
+ */
+
+struct _GVfsBackendGphoto2
+{
+  GVfsBackend parent_instance;
+
+  /* a gphoto2 specific identifier for the gphoto2 camera such as usb:001,041 */
+  char *gphoto2_port;
+  GPContext *context;
+  Camera *camera;
+
+  /* see comment in ensure_ignore_prefix() */
+  char *ignore_prefix;
+
+  int num_open_files;
+
+  DBusConnection *dbus_connection;
+  LibHalContext *hal_ctx;
+  char *hal_udi;
+  char *hal_name;
+  char *hal_icon_name;
+
+  /* CACHES */
+
+  /* fully qualified path -> GFileInfo */
+  GHashTable *info_cache;
+
+  /* dir name -> CameraList of (sub-) directory names in given directory */
+  GHashTable *dir_name_cache;
+
+  /* dir name -> CameraList of file names in given directory */
+  GHashTable *file_name_cache;
+};
+
+G_DEFINE_TYPE (GVfsBackendGphoto2, g_vfs_backend_gphoto2, G_VFS_TYPE_BACKEND);
+
+static GError *
+get_error_from_gphoto2 (const char *message, int gphoto2_error_code)
+{
+  GError *error;
+  /* TODO: properly map error number */
+  error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, _("%s: %d: %s"), 
+                       message,
+                       gphoto2_error_code, 
+                       gp_result_as_string (gphoto2_error_code));
+  return error;
+}
+
+static void
+release_device (GVfsBackendGphoto2 *gphoto2_backend)
+{
+  g_free (gphoto2_backend->gphoto2_port);
+  gphoto2_backend->gphoto2_port = NULL;
+
+  if (gphoto2_backend->context != NULL)
+    {
+      gp_context_unref (gphoto2_backend->context);
+      gphoto2_backend->context = NULL;
+    }
+
+  if (gphoto2_backend->camera != NULL)
+    {
+      gp_camera_unref (gphoto2_backend->camera);
+      gphoto2_backend->camera = NULL;
+    }
+
+  if (gphoto2_backend->dbus_connection != NULL)
+    {
+      dbus_connection_close (gphoto2_backend->dbus_connection);
+      dbus_connection_unref (gphoto2_backend->dbus_connection);
+      gphoto2_backend->dbus_connection = NULL;
+    }
+
+  if (gphoto2_backend->hal_ctx != NULL)
+    {
+      libhal_ctx_free (gphoto2_backend->hal_ctx);
+      gphoto2_backend->hal_ctx = NULL;
+
+    }
+  g_free (gphoto2_backend->hal_udi);
+  gphoto2_backend->hal_udi = NULL;
+  g_free (gphoto2_backend->hal_name);
+  gphoto2_backend->hal_name = NULL;
+  g_free (gphoto2_backend->hal_icon_name);
+  gphoto2_backend->hal_icon_name = NULL;
+
+  g_free (gphoto2_backend->ignore_prefix);
+  gphoto2_backend->ignore_prefix = NULL;
+
+  if (gphoto2_backend->info_cache != NULL)
+    {
+      g_hash_table_unref (gphoto2_backend->info_cache);
+      gphoto2_backend->info_cache = NULL;
+    }
+  if (gphoto2_backend->dir_name_cache != NULL)
+    {
+      g_hash_table_unref (gphoto2_backend->dir_name_cache);
+      gphoto2_backend->dir_name_cache = NULL;
+    }
+  if (gphoto2_backend->file_name_cache != NULL)
+    {
+      g_hash_table_unref (gphoto2_backend->file_name_cache);
+      gphoto2_backend->file_name_cache = NULL;
+    }
+}
+
+static void
+g_vfs_backend_gphoto2_finalize (GObject *object)
+{
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (object);
+
+  /*g_warning ("finalizing %p", object);*/
+
+  release_device (gphoto2_backend);
+
+  if (G_OBJECT_CLASS (g_vfs_backend_gphoto2_parent_class)->finalize)
+    (*G_OBJECT_CLASS (g_vfs_backend_gphoto2_parent_class)->finalize) (object);
+}
+
+static void
+g_vfs_backend_gphoto2_init (GVfsBackendGphoto2 *gphoto2_backend)
+{
+  GVfsBackend *backend = G_VFS_BACKEND (gphoto2_backend);
+  GMountSpec *mount_spec;
+
+  /*g_warning ("initing %p", gphoto2_backend);*/
+
+  g_vfs_backend_set_display_name (backend, "gphoto2");
+
+  mount_spec = g_mount_spec_new ("gphoto2");
+  g_vfs_backend_set_mount_spec (backend, mount_spec);
+  g_mount_spec_unref (mount_spec);
+}
+
+static char *
+compute_icon_name (GVfsBackendGphoto2 *gphoto2_backend)
+{
+  char *result;
+
+  if (gphoto2_backend->hal_icon_name == NULL)
+    {
+      result = g_strdup_printf ("camera");
+    }
+  else
+    {
+      result = g_strdup (gphoto2_backend->hal_icon_name);
+    }
+
+  return result;
+}
+
+static char *
+compute_display_name (GVfsBackendGphoto2 *gphoto2_backend)
+{
+  char *result;
+
+  if (gphoto2_backend->hal_name == NULL)
+    {
+      result = g_strdup_printf (_("Digital Camera (%s)"), gphoto2_backend->gphoto2_port);
+    }
+  else
+    {
+      result = g_strdup (gphoto2_backend->hal_name);
+    }
+
+  return result;
+}
+
+
+static void
+find_udi_for_device (GVfsBackendGphoto2 *gphoto2_backend)
+{
+  int num_camera_devices;
+  int num_mtp_devices;
+  int num_devices;
+  char **camera_devices;
+  char **mtp_devices;
+  char **devices;
+  int n, m;
+  int usb_bus_num;
+  int usb_device_num;
+  char **tokens;
+  char *endp;
+
+  gphoto2_backend->hal_udi = NULL;
+
+  /* parse the usb:001,041 string */
+
+  if (!g_str_has_prefix (gphoto2_backend->gphoto2_port, "usb:"))
+    {
+      return;
+    }
+
+  tokens = g_strsplit (gphoto2_backend->gphoto2_port + 4, ",", 0);
+  if (g_strv_length (tokens) != 2)
+    {
+      g_strfreev (tokens);
+      return;
+    }
+
+  usb_bus_num = strtol (tokens[0], &endp, 10);
+  if (*endp != '\0')
+    {
+      g_strfreev (tokens);
+      return;
+    }
+
+  usb_device_num = strtol (tokens[1], &endp, 10);
+  if (*endp != '\0')
+    {
+      g_strfreev (tokens);
+      return;
+    }
+
+  g_strfreev (tokens);
+
+  /*g_warning ("Parsed '%s' into bus=%d device=%d", gphoto2_backend->gphoto2_port, usb_bus_num, usb_device_num);*/
+
+  camera_devices = libhal_find_device_by_capability (gphoto2_backend->hal_ctx,
+                                                     "camera",
+                                                     &num_camera_devices,
+                                                     NULL);
+  mtp_devices = libhal_find_device_by_capability (gphoto2_backend->hal_ctx,
+                                                  "portable_audio_player",
+                                                  &num_mtp_devices,
+                                                  NULL);
+  for (m = 0; m < 2 && gphoto2_backend->hal_udi == NULL; m++)
+    {
+      devices = m == 0 ? camera_devices : mtp_devices;
+      num_devices = m == 0 ? num_camera_devices : num_mtp_devices;
+
+      if (devices != NULL)
+        {
+          for (n = 0; n < num_devices && gphoto2_backend->hal_udi == NULL; n++)
+            {
+              char *udi = devices[n];
+              LibHalPropertySet *ps;
+              
+              ps = libhal_device_get_all_properties (gphoto2_backend->hal_ctx, udi, NULL);
+              if (ps != NULL)
+                {
+                  const char *subsystem;
+              
+                  subsystem = libhal_ps_get_string (ps, "info.subsystem");
+                  if (subsystem != NULL && strcmp (subsystem, "usb") == 0)
+                    {
+                      int device_usb_bus_num;
+                      int device_usb_device_num;
+                      const char *icon_from_hal;
+                      const char *name_from_hal;
+                      
+                      device_usb_bus_num = libhal_ps_get_int32 (ps, "usb.bus_number");
+                      device_usb_device_num = libhal_ps_get_int32 (ps, "usb.linux.device_number");
+                      icon_from_hal = libhal_ps_get_string (ps, "info.desktop.icon");
+                      name_from_hal = libhal_ps_get_string (ps, "info.desktop.name");
+                      
+                      /*g_warning ("looking at usb device '%s' with bus=%d, device=%d", 
+                        udi, device_usb_bus_num, device_usb_device_num);*/
+                      
+                      if (device_usb_bus_num == usb_bus_num && 
+                          device_usb_device_num == usb_device_num)
+                        {
+                          char *name;
+                          const char *parent_udi;
+                          LibHalPropertySet *ps2;
+
+                          /*g_warning ("udi '%s' is the one!", gphoto2_backend->hal_udi);*/
+                          
+                          /* IMPORTANT: 
+                           * 
+                           * Keep this naming code in sync with
+                           *
+                           *   hal/ghalvolume;do_update_from_hal_for_camera() 
+                           */
+                          name = NULL;
+                          parent_udi = libhal_ps_get_string (ps, "info.parent");
+                          if (name_from_hal != NULL)
+                            {
+                              name = g_strdup (name_from_hal);
+                            }
+                          else if (parent_udi != NULL)
+                            {
+                              ps2 = libhal_device_get_all_properties (gphoto2_backend->hal_ctx, parent_udi, NULL);
+                              if (ps2 != NULL)
+                                {
+                                  const char *vendor;
+                                  const char *product;
+                                  
+                                  vendor = libhal_ps_get_string (ps2, "usb_device.vendor");
+                                  product = libhal_ps_get_string (ps2, "usb_device.product");
+                                  if (vendor == NULL)
+                                    {
+                                      if (product != NULL)
+                                        name = g_strdup (product);
+                                    }
+                                  else
+                                    {
+                                      if (product != NULL)
+                                        name = g_strdup_printf ("%s %s", vendor, product);
+                                      else
+                                        name = g_strdup_printf (_("%s Camera"), vendor);
+                                    }
+                                  libhal_free_property_set (ps2);
+                                }
+                            }
+                          if (name == NULL)
+                            name = g_strdup (_("Camera"));
+                          
+                          gphoto2_backend->hal_udi = g_strdup (udi);
+                          gphoto2_backend->hal_name = name;
+                          if (icon_from_hal != NULL)
+                            {
+                              gphoto2_backend->hal_icon_name = g_strdup (icon_from_hal);
+                            }
+                          else
+                            {
+                              if (m == 1)
+                                {
+                                  gphoto2_backend->hal_icon_name = g_strdup ("multimedia-player");
+                                }
+                              else
+                                {
+                                  gphoto2_backend->hal_icon_name = g_strdup ("camera");
+                                }
+                            }
+                        }
+                      
+                    }
+                  
+                  libhal_free_property_set (ps);
+                }
+            }
+          libhal_free_string_array (devices);
+        }
+    }
+}
+
+static void
+_hal_device_removed (LibHalContext *hal_ctx, const char *udi)
+{
+  GVfsBackendGphoto2 *gphoto2_backend;
+
+  gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (libhal_ctx_get_user_data (hal_ctx));
+
+  if (gphoto2_backend->hal_udi != NULL && strcmp (udi, gphoto2_backend->hal_udi) == 0)
+    {
+      /*g_warning ("we have been removed!");*/
+
+      /* TODO: need a cleaner way to force unmount ourselves */
+      exit (1);
+    }
+}
+
+static void
+do_mount (GVfsBackend *backend,
+	  GVfsJobMount *job,
+	  GMountSpec *mount_spec,
+	  GMountSource *mount_source,
+	  gboolean is_automount)
+{
+  char *fuse_name;
+  char *display_name;
+  char *icon_name;
+  const char *host;
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+  GError *error = NULL;
+  GMountSpec *gphoto2_mount_spec;
+  int rc;
+  GPPortInfo info;
+  GPPortInfoList *il = NULL;
+  int n;
+  DBusError dbus_error;
+
+  /*g_warning ("do_mount %p", gphoto2_backend);*/
+
+  /* setup libhal */
+
+  dbus_error_init (&dbus_error);
+  gphoto2_backend->dbus_connection = dbus_bus_get_private (DBUS_BUS_SYSTEM, &dbus_error);
+  if (dbus_error_is_set (&dbus_error))
+    {
+      release_device (gphoto2_backend);
+      dbus_error_free (&dbus_error);
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot connect to the system bus"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+  
+  gphoto2_backend->hal_ctx = libhal_ctx_new ();
+  if (gphoto2_backend->hal_ctx == NULL)
+    {
+      release_device (gphoto2_backend);
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot create libhal context"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  _g_dbus_connection_integrate_with_main (gphoto2_backend->dbus_connection);
+  libhal_ctx_set_dbus_connection (gphoto2_backend->hal_ctx, gphoto2_backend->dbus_connection);
+  
+  if (!libhal_ctx_init (gphoto2_backend->hal_ctx, &dbus_error))
+    {
+      release_device (gphoto2_backend);
+      dbus_error_free (&dbus_error);
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot initialize libhal"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return;
+    }
+
+  libhal_ctx_set_device_removed (gphoto2_backend->hal_ctx, _hal_device_removed);
+  libhal_ctx_set_user_data (gphoto2_backend->hal_ctx, gphoto2_backend);
+
+  /* setup gphoto2 */
+
+  host = g_mount_spec_get (mount_spec, "host");
+  /*g_warning ("host='%s'", host);*/
+  if (host == NULL || strlen (host) < 3 || host[0] != '[' || host[strlen (host) - 1] != ']')
+    {
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("No drive specified"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+
+  gphoto2_backend->gphoto2_port = g_strdup (host + 1);
+  gphoto2_backend->gphoto2_port[strlen (gphoto2_backend->gphoto2_port) - 1] = '\0';
+
+  /*g_warning ("decoded host='%s'", gphoto2_backend->gphoto2_port);*/
+
+  find_udi_for_device (gphoto2_backend);
+
+  gphoto2_backend->context = gp_context_new ();
+  if (gphoto2_backend->context == NULL)
+    {
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot create gphoto2 context"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+
+  rc = gp_camera_new (&(gphoto2_backend->camera));
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error creating camera", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+
+
+  il = NULL;
+  
+  rc = gp_port_info_list_new (&il);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error creating port info list", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+
+  rc = gp_port_info_list_load (il);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error loading info list", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+
+  /*g_warning ("gphoto2_port='%s'", gphoto2_backend->gphoto2_port);*/
+
+  n = gp_port_info_list_lookup_path (il, gphoto2_backend->gphoto2_port);
+  if (n == GP_ERROR_UNKNOWN_PORT)
+    {
+      error = get_error_from_gphoto2 ("Error looking up port from port info list", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+
+  rc = gp_port_info_list_get_info (il, n, &info);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error getting port info from port info list", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+
+  /*g_warning ("'%s' '%s' '%s'",  info.name, info.path, info.library_filename);*/
+  
+  /* set port */
+  rc = gp_camera_set_port_info (gphoto2_backend->camera, info);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error setting port info", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+  gp_port_info_list_free(il);
+
+  rc = gp_camera_init (gphoto2_backend->camera, gphoto2_backend->context);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error initializing camera", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+
+  fuse_name = g_strdup_printf (_("gphoto2 mount on %s"), gphoto2_backend->gphoto2_port);
+  icon_name = compute_icon_name (gphoto2_backend);
+  display_name = compute_display_name (gphoto2_backend);
+  g_vfs_backend_set_stable_name (backend, fuse_name);
+  g_vfs_backend_set_display_name (backend, display_name);
+  g_vfs_backend_set_icon_name (backend, icon_name);
+  g_free (display_name);
+  g_free (icon_name);
+  g_free (fuse_name);
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  gphoto2_mount_spec = g_mount_spec_new ("gphoto2");
+  g_mount_spec_set (gphoto2_mount_spec, "host", host);
+  g_vfs_backend_set_mount_spec (backend, gphoto2_mount_spec);
+  g_mount_spec_unref (gphoto2_mount_spec);
+
+  gphoto2_backend->info_cache = g_hash_table_new_full (g_str_hash,
+                                                       g_str_equal,
+                                                       g_free,
+                                                       g_object_unref);
+
+  gphoto2_backend->dir_name_cache = g_hash_table_new_full (g_str_hash,
+                                                           g_str_equal,
+                                                           g_free,
+                                                           (GDestroyNotify) gp_list_unref);
+
+  gphoto2_backend->file_name_cache = g_hash_table_new_full (g_str_hash,
+                                                            g_str_equal,
+                                                            g_free,
+                                                            (GDestroyNotify) gp_list_unref);
+
+  /*g_warning ("mounted %p", gphoto2_backend);*/
+}
+
+static gboolean
+try_mount (GVfsBackend *backend,
+           GVfsJobMount *job,
+           GMountSpec *mount_spec,
+           GMountSource *mount_source,
+           gboolean is_automount)
+{
+  const char *host;
+  GError *error = NULL;
+  GMountSpec *gphoto2_mount_spec;
+
+  /*g_warning ("try_mount %p", backend);*/
+
+  /* TODO: Hmm.. apparently we have to set the mount spec in
+   * try_mount(); doing it in mount() do_won't work.. 
+   */
+  host = g_mount_spec_get (mount_spec, "host");
+  /*g_warning ("tm host=%s", host);*/
+  if (host == NULL)
+    {
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("No camera specified"));
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      return TRUE;
+    }
+
+  gphoto2_mount_spec = g_mount_spec_new ("gphoto2");
+  g_mount_spec_set (gphoto2_mount_spec, "host", host);
+  g_vfs_backend_set_mount_spec (backend, gphoto2_mount_spec);
+  g_mount_spec_unref (gphoto2_mount_spec);
+  return FALSE;
+}
+
+
+static void
+do_unmount (GVfsBackend *backend,
+            GVfsJobUnmount *job)
+{
+  GError *error;
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+
+  if (gphoto2_backend->num_open_files > 0)
+    {
+      error = g_error_new (G_IO_ERROR, G_IO_ERROR_BUSY, 
+                           _("File system is busy: %d open files"), gphoto2_backend->num_open_files);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      return;
+    }
+
+  release_device (gphoto2_backend);
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  //g_warning ("unmounted %p", backend);
+}
+
+/* The PTP gphoto2 backend puts an annoying virtual store_00010001
+ * directory in the root (in fact 00010001 can be any hexedecimal
+ * digit).
+ *
+ * We want to skip that as the x-content detection expects to find the
+ * DCIM/ folder. As such, this function tries to detect the presence
+ * of such a folder in the root and, if found, sets a variable that is
+ * prepended to any path passed to libgphoto2. This is cached for as
+ * long as we got a connection to libgphoto2. If this operation fails
+ * then the passed job will be cancelled and this function will return
+ * FALSE.
+ *
+ * IMPORTANT: *ANY* method called by the gvfs core needs to call this
+ * function before doing anything. If FALSE is returned the function
+ * just needs to return to the gvfs core.
+ */
+static gboolean
+ensure_ignore_prefix (GVfsBackendGphoto2 *gphoto2_backend, GVfsJob *job)
+{
+  int rc;
+  char *prefix;
+  GError *error;
+  CameraList *list;
+
+  /* already set */
+  if (gphoto2_backend->ignore_prefix != NULL)
+    return TRUE;
+
+  /* check folders in / - if there is exactly one folder of the form "store_" followed by eight
+   * hexadecimal digits.. then use that as ignore_prefix.. otherwise don't use anything 
+   */
+
+  gp_list_new (&list);
+  rc = gp_camera_folder_list_folders (gphoto2_backend->camera, 
+                                      "/", 
+                                      list, 
+                                      gphoto2_backend->context);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error listing folders to figure out ignore prefix", rc);
+      g_vfs_job_failed_from_error (job, error);
+      return FALSE;
+  }  
+
+  prefix = NULL;
+
+  if (gp_list_count (list) == 1)
+    {
+      char *name;
+      const char *s;
+      unsigned int n;
+
+      gp_list_get_name (list, 0, &s);
+
+      name = g_ascii_strdown (s, -1);
+      if (g_str_has_prefix (name, "store_") && strlen (name) == 14)
+        {
+          for (n = 6; n < 14; n++)
+            {
+              if (!g_ascii_isxdigit (name[n]))
+                {
+                  break;
+                }
+            }
+          if (n == 14)
+            {
+              prefix = g_strconcat ("/", name, NULL);
+            }
+        }
+
+      g_free (name);
+    }
+  gp_list_free (list);
+
+  if (prefix == NULL)
+    gphoto2_backend->ignore_prefix = g_strdup ("");
+  else
+    gphoto2_backend->ignore_prefix = prefix;
+
+  return TRUE;
+}
+
+typedef struct {
+  CameraFile *file;
+  const char *data;
+  unsigned long int size;
+  unsigned long int cursor;
+} ReadHandle;
+
+static void
+free_read_handle (ReadHandle *read_handle)
+{
+  if (read_handle->file != NULL)
+    gp_file_unref (read_handle->file);
+  g_free (read_handle);
+}
+
+static void
+do_open_for_read (GVfsBackend *backend,
+                  GVfsJobOpenForRead *job,
+                  const char *filename)
+{
+  int rc;
+  GError *error;
+  ReadHandle *read_handle;
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+  char *s;
+  char *dir;
+  char *name;
+
+  if (!ensure_ignore_prefix (gphoto2_backend, G_VFS_JOB (job)))
+    return;
+
+  s = g_path_get_dirname (filename);
+  dir = g_strconcat (gphoto2_backend->ignore_prefix, s, NULL);
+  g_free (s);
+  name = g_path_get_basename (filename);
+
+  /*g_warning ("open_for_read (%s)", filename);*/
+
+  read_handle = g_new0 (ReadHandle, 1);
+  rc = gp_file_new (&read_handle->file);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error creating file", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      free_read_handle (read_handle);
+      goto out;
+    }
+
+  rc = gp_camera_file_get (gphoto2_backend->camera,
+                           dir,
+                           name,
+                           GP_FILE_TYPE_NORMAL,
+                           read_handle->file,
+                           gphoto2_backend->context);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error getting file", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      free_read_handle (read_handle);
+      goto out;
+    }
+
+  rc = gp_file_get_data_and_size (read_handle->file, &read_handle->data, &read_handle->size);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 ("Error getting data from file", rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      free_read_handle (read_handle);
+      goto out;
+    }
+
+  /*g_warning ("data=%p size=%ld", read_handle->data, read_handle->size);*/
+
+  gphoto2_backend->num_open_files++;
+
+  read_handle->cursor = 0;
+
+  g_vfs_job_open_for_read_set_can_seek (job, TRUE);
+  g_vfs_job_open_for_read_set_handle (job, GINT_TO_POINTER (read_handle));
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+  g_free (name);
+  g_free (dir);
+}
+
+static void
+do_read (GVfsBackend *backend,
+         GVfsJobRead *job,
+         GVfsBackendHandle handle,
+         char *buffer,
+         gsize bytes_requested)
+{
+  //GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+  ReadHandle *read_handle = (ReadHandle *) handle;
+  gsize bytes_left;
+  gsize bytes_to_copy;
+
+  /*g_warning ("do_read (%d @ %ld of %ld)", bytes_requested, read_handle->cursor, read_handle->size);*/
+
+  if (read_handle->cursor >= read_handle->size)
+    {
+      bytes_to_copy = 0;
+      goto out;
+    }
+
+  bytes_left = read_handle->size - read_handle->cursor;
+  if (bytes_requested > bytes_left)
+    bytes_to_copy = bytes_left;
+  else
+    bytes_to_copy = bytes_requested;
+
+  memcpy (buffer, read_handle->data + read_handle->cursor, bytes_to_copy);
+  read_handle->cursor += bytes_to_copy;
+
+ out:
+  
+  g_vfs_job_read_set_size (job, bytes_to_copy);
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+static void
+do_seek_on_read (GVfsBackend *backend,
+		 GVfsJobSeekRead *job,
+		 GVfsBackendHandle handle,
+		 goffset    offset,
+		 GSeekType  type)
+{
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+  ReadHandle *read_handle = (ReadHandle *) handle;
+  long new_offset;
+
+  /*g_warning ("seek_on_read (%d, %d)", (int)offset, type);*/
+
+  switch (type)
+    {
+    default:
+    case G_SEEK_SET:
+      new_offset = offset;
+      break;
+    case G_SEEK_CUR:
+      new_offset = read_handle->cursor + offset;
+      break;
+    case G_SEEK_END:
+      new_offset = read_handle->size + offset;
+      break;
+    }
+
+  if (new_offset < 0 || new_offset >= read_handle->size)
+    {
+      g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+			G_IO_ERROR_FAILED,
+			_("Error seeking in stream on camera %s"), gphoto2_backend->gphoto2_port);
+    }
+  else
+    {
+      read_handle->cursor = new_offset;
+      
+      g_vfs_job_seek_read_set_offset (job, offset);
+      g_vfs_job_succeeded (G_VFS_JOB (job));
+    }
+}
+
+static void
+do_close_read (GVfsBackend *backend,
+	       GVfsJobCloseRead *job,
+	       GVfsBackendHandle handle)
+{
+  ReadHandle *read_handle = (ReadHandle *) handle;
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+
+  /*g_warning ("close ()");*/
+
+  free_read_handle (read_handle);
+
+  gphoto2_backend->num_open_files--;
+  
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+/* the passed 'dir' variable contains ignore_prefix */
+static gboolean
+_set_info (GVfsBackendGphoto2 *gphoto2_backend, const char *dir, const char *name, GFileInfo *info, GError **error)
+{
+  int rc;
+  gboolean ret;
+  CameraFileInfo gp_info;
+  char *full_path;
+  GFileInfo *cached_info;
+  GTimeVal mtime;
+  char *mime_type;
+  GIcon *icon;
+
+  /*g_warning ("_set_info(); dir='%s', name='%s'", dir, name);*/
+
+  ret = FALSE;
+
+  /* look up cache */
+  full_path = g_strconcat (dir, "/", name, NULL);
+  cached_info = g_hash_table_lookup (gphoto2_backend->info_cache, full_path);
+  if (cached_info != NULL)
+    {
+      /*g_warning ("Using cached info for '%s'", full_path);*/
+      g_file_info_copy_into (cached_info, info);
+      ret = TRUE;
+      goto out;
+    }
+
+  rc = gp_camera_file_get_info (gphoto2_backend->camera,
+                                dir,
+                                name,
+                                &gp_info,
+                                gphoto2_backend->context);
+  if (rc != 0)
+    {
+      CameraList *list;
+      unsigned int n;
+
+      /* gphoto2 doesn't know about this file.. it may be a folder; try that */
+
+      gp_list_new (&list);
+      rc = gp_camera_folder_list_folders (gphoto2_backend->camera, 
+                                          dir, 
+                                          list, 
+                                          gphoto2_backend->context);
+      if (rc != 0)
+        {
+          *error = get_error_from_gphoto2 ("Error listing folders", rc);
+          goto out;
+        }  
+      
+      for (n = 0; n < gp_list_count (list); n++) 
+        {
+          const char *folder_name;
+
+          gp_list_get_name (list, n, &folder_name);
+          
+          /*g_warning ("Looking at folder_name='%s' for dir='%s'", folder_name, dir);*/
+          
+          if (strcmp (name, folder_name) != 0)
+            continue;
+          
+          /*g_warning ("Got it");*/
+          
+          g_file_info_set_name (info, name);
+          g_file_info_set_display_name (info, name);
+          icon = g_themed_icon_new ("folder");
+          g_file_info_set_icon (info, icon);
+          g_object_unref (icon);
+          g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+          g_file_info_set_content_type (info, "inode/directory");
+          g_file_info_set_size (info, 0);
+          g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE);
+          g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
+          g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
+          g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE);
+          g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
+          g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE); 
+          
+          gp_list_free (list);
+          ret = TRUE;
+          goto add_to_cache;
+        }
+      gp_list_free (list);
+
+      /* nope, not a folder either.. error out.. */
+      
+      *error = get_error_from_gphoto2 ("Error getting file info", rc);
+      goto out;
+    }
+
+  g_file_info_set_name (info, name);
+  g_file_info_set_display_name (info, name);
+  g_file_info_set_file_type (info, G_FILE_TYPE_REGULAR);
+
+  if (gp_info.file.fields & GP_FILE_INFO_SIZE)
+    {
+      g_file_info_set_size (info, gp_info.file.size);
+    }
+  else
+    {
+      /* not really sure this is the right thing to do... */
+      g_file_info_set_size (info, 0);
+    }
+
+  /* TODO: We really should sniff the file / look at file extensions
+   * instead of relying on gp_info.file.type... but sniffing the file
+   * is no fun since we (currently) can't do partial reads with the
+   * libgphoto2 API :-/
+   */
+  mime_type = NULL;
+  if (gp_info.file.fields & GP_FILE_INFO_TYPE)
+    {
+      /* Nasty (or cute?) hack to get correct mime types on my Sansa
+       * Sandisk player; it returns bogus "application/x-unknown" mime
+       * type. Go Sandisk!
+       */
+      if (strcmp (gp_info.file.type, "application/x-unknown") == 0)
+        {
+          mime_type = g_content_type_guess (name, NULL, 0, NULL);
+#if 0
+          if (g_str_has_suffix (name, ".mp3") || g_str_has_suffix (name, ".MP3"))
+            {
+              mime_type = g_strdup ("audio/mpeg");
+            }
+          else if (g_str_has_suffix (name, ".mov") || g_str_has_suffix (name, ".MOV"))
+            {
+              mime_type = g_strdup ("video/quicktime");
+            }
+          else
+            {
+              /* application/x-unknown is bogus; fall back to default specified below */
+            }
+#endif
+        }
+      else
+        {
+          mime_type = g_strdup (gp_info.file.type);
+        }
+    }
+  if (mime_type == NULL)
+    mime_type = g_strdup ("application/octet-stream");
+  g_file_info_set_content_type (info, mime_type);
+
+  icon = g_content_type_get_icon (mime_type);
+  /*g_warning ("got icon %p for mime_type '%s'", icon, mime_type);*/
+  if (icon != NULL)
+    {
+      g_file_info_set_icon (info, icon);
+      g_object_unref (icon);
+    }
+  g_free (mime_type);
+
+
+  if (gp_info.file.fields & GP_FILE_INFO_MTIME)
+    mtime.tv_sec = gp_info.file.mtime;
+  else
+    mtime.tv_sec = 0;
+  mtime.tv_usec = 0;
+  g_file_info_set_modification_time (info, &mtime);
+
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE);
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, FALSE);
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE); 
+
+  ret = TRUE;
+
+ add_to_cache:
+  /* add this sucker to the cache */
+  if (ret == TRUE)
+    {
+      g_hash_table_insert (gphoto2_backend->info_cache, g_strdup (full_path), g_file_info_dup (info));
+    }
+
+ out:
+  g_free (full_path);
+  return ret;
+}
+
+static void
+do_query_info (GVfsBackend *backend,
+	       GVfsJobQueryInfo *job,
+	       const char *filename,
+	       GFileQueryInfoFlags flags,
+	       GFileInfo *info,
+	       GFileAttributeMatcher *matcher)
+{
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+  GError *error;
+
+  if (!ensure_ignore_prefix (gphoto2_backend, G_VFS_JOB (job)))
+    return;
+
+  /*g_warning ("get_file_info (%s)", filename);*/
+
+  if (strcmp (filename, "/") == 0)
+    {
+      GIcon *icon;
+      char *display_name;
+      display_name = compute_display_name (gphoto2_backend);
+      g_file_info_set_display_name (info, display_name);
+      g_free (display_name);
+      g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+      g_file_info_set_content_type (info, "inode/directory");
+      g_file_info_set_size (info, 0);
+      icon = g_themed_icon_new ("folder");
+      g_file_info_set_icon (info, icon);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE); 
+      g_object_unref (icon);
+      g_vfs_job_succeeded (G_VFS_JOB (job));
+    }
+  else
+    {
+      char *s;
+      char *dir;
+      char *name;
+
+      s = g_path_get_dirname (filename);
+      dir = g_strconcat (gphoto2_backend->ignore_prefix, s, NULL);
+      g_free (s);
+      name = g_path_get_basename (filename);
+
+      if (!_set_info (gphoto2_backend, dir, name, info, &error))
+        {
+          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+        }
+      else
+        {
+          g_vfs_job_succeeded (G_VFS_JOB (job));
+        }
+
+      g_free (name);
+      g_free (dir);
+    }
+  
+}
+
+static void
+do_enumerate (GVfsBackend *backend,
+              GVfsJobEnumerate *job,
+              const char *given_filename,
+              GFileAttributeMatcher *matcher,
+              GFileQueryInfoFlags flags)
+{
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+  GFileInfo *info;
+  GList *l;
+  GError *error;
+  CameraList *list;
+  int n;
+  int rc;
+  char *filename;
+  gboolean using_cached_dir_list;
+  gboolean using_cached_file_list;
+
+  if (!ensure_ignore_prefix (gphoto2_backend, G_VFS_JOB (job)))
+    return;
+
+  l = NULL;
+  using_cached_dir_list = FALSE;
+  using_cached_file_list = FALSE;
+
+  filename = g_strconcat (gphoto2_backend->ignore_prefix, given_filename, NULL);
+  /*g_warning ("enumerate (%s) (%s)", given_filename, filename);*/
+
+  /* first, list the folders */
+  list = g_hash_table_lookup (gphoto2_backend->dir_name_cache, filename);
+  if (list == NULL)
+    {
+      gp_list_new (&list);
+      rc = gp_camera_folder_list_folders (gphoto2_backend->camera, 
+                                          filename, 
+                                          list, 
+                                          gphoto2_backend->context);
+      if (rc != 0)
+        {
+          error = get_error_from_gphoto2 ("Error listing folders", rc);
+          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+          g_free (filename);
+          return;
+        }  
+    }
+  else
+    {
+      /*g_warning ("Using cached dirlist for dir '%s'", filename);*/
+      gp_list_ref (list);
+      using_cached_dir_list = TRUE;
+    }
+  for (n = 0; n < gp_list_count (list); n++) 
+    {
+      const char *name;
+      GIcon *icon;
+
+      gp_list_get_name (list, n, &name);
+
+      /*g_warning ("enum '%s'", name);*/
+
+      info = g_file_info_new ();
+      g_file_info_set_name (info, name);
+      g_file_info_set_display_name (info, name);
+
+      icon = g_themed_icon_new ("folder");
+      g_file_info_set_icon (info, icon);
+      g_object_unref (icon);
+
+      g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
+      g_file_info_set_content_type (info, "inode/directory");
+      g_file_info_set_size (info, 0);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
+      g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, FALSE); 
+
+      l = g_list_append (l, info);
+    }
+  if (!using_cached_dir_list)
+    {
+      gp_list_ref (list);
+      g_hash_table_insert (gphoto2_backend->dir_name_cache, g_strdup (filename), list);
+    }
+  gp_list_unref (list);
+
+
+  /* then list the files in each folder */
+  list = g_hash_table_lookup (gphoto2_backend->file_name_cache, filename);
+  if (list == NULL)
+    {
+      gp_list_new (&list);
+      rc = gp_camera_folder_list_files (gphoto2_backend->camera, 
+                                        filename, 
+                                        list, 
+                                        gphoto2_backend->context);
+      if (rc != 0)
+        {
+          error = get_error_from_gphoto2 ("Error listing files in folder", rc);
+          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+          g_free (filename);
+          return;
+        }
+    }
+  else
+    {
+      /*g_warning ("Using cached file list for dir '%s'", filename);*/
+      gp_list_ref (list);
+      using_cached_file_list = TRUE;
+    }
+  for (n = 0; n < gp_list_count (list); n++) 
+    {
+      const char *name;
+
+      gp_list_get_name (list, n, &name);
+
+      info = g_file_info_new ();
+      if (!_set_info (gphoto2_backend, filename, name, info, &error))
+        {
+          g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+          g_list_foreach (l, (GFunc) g_object_unref, NULL);
+          g_list_free (l);
+          gp_list_free (list);
+          return;
+        }
+      l = g_list_append (l, info);
+    }
+  if (!using_cached_file_list)
+    {
+      gp_list_ref (list);
+      g_hash_table_insert (gphoto2_backend->file_name_cache, g_strdup (filename), list);
+    }
+  gp_list_unref (list);
+
+  /* and we're done */
+
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+  g_vfs_job_enumerate_add_infos (job, l);
+  g_list_foreach (l, (GFunc) g_object_unref, NULL);
+  g_list_free (l);
+  g_vfs_job_enumerate_done (job);
+
+  g_free (filename);
+}
+
+static void
+do_query_fs_info (GVfsBackend *backend,
+		  GVfsJobQueryFsInfo *job,
+		  const char *filename,
+		  GFileInfo *info,
+		  GFileAttributeMatcher *attribute_matcher)
+{
+  int rc;
+  GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+
+  g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "gphoto2");
+  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE);
+  g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL);
+
+  int num_storage_info;
+  CameraStorageInformation *storage_info;
+
+  rc = gp_camera_get_storageinfo (gphoto2_backend->camera, &storage_info, &num_storage_info, gphoto2_backend->context);
+  if (rc == 0)
+    {
+      if (num_storage_info >= 1)
+        {
+
+          /*g_warning ("capacity = %ld", storage_info[0].capacitykbytes);*/
+          /*g_warning ("free = %ld", storage_info[0].freekbytes);*/
+
+          /* for now we only support a single storage head */
+          if (storage_info[0].fields & GP_STORAGEINFO_MAXCAPACITY)
+            {
+              g_file_info_set_attribute_uint64 (info, 
+                                                G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, 
+                                                storage_info[0].capacitykbytes * 1024);
+            }
+          if (storage_info[0].fields & GP_STORAGEINFO_FREESPACEKBYTES)
+            {
+              g_file_info_set_attribute_uint64 (info, 
+                                                G_FILE_ATTRIBUTE_FILESYSTEM_FREE, 
+                                                storage_info[0].freekbytes * 1024);
+            }
+          
+        }
+      /*g_warning ("got %d storage_info objects", num_storage_info);*/
+    }
+
+  
+  g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+static void
+g_vfs_backend_gphoto2_class_init (GVfsBackendGphoto2Class *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
+  
+  gobject_class->finalize = g_vfs_backend_gphoto2_finalize;
+
+  backend_class->try_mount = try_mount;
+  backend_class->mount = do_mount;
+  backend_class->unmount = do_unmount;
+  backend_class->open_for_read = do_open_for_read;
+  backend_class->read = do_read;
+  backend_class->seek_on_read = do_seek_on_read;
+  backend_class->close_read = do_close_read;
+  backend_class->query_info = do_query_info;
+  backend_class->enumerate = do_enumerate;
+  backend_class->query_fs_info = do_query_fs_info;
+}
Index: gio/glocalfileinfo.c
===================================================================
--- gio/glocalfileinfo.c	(revision 6545)
+++ gio/glocalfileinfo.c	(working copy)
@@ -1,3 +1,5 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
 /* GIO - GLib Input, Output and Streaming Library
  * 
  * Copyright (C) 2006-2007 Red Hat, Inc.
@@ -1488,48 +1490,31 @@
 	  if (g_file_attribute_matcher_matches (attribute_matcher,
 						G_FILE_ATTRIBUTE_STANDARD_ICON))
 	    {
-	      char *mimetype_icon, *generic_mimetype_icon, *type_icon, *p;
-	      char *icon_names[3];
 	      GIcon *icon;
-	      int i;
 
-	      mimetype_icon = g_strdup (content_type);
-	      
-	      while ((p = strchr (mimetype_icon, '/')) != NULL)
-		*p = '-';
+              icon = g_content_type_get_icon (content_type);
+              if (icon != NULL)
+                {
+                  if (g_type_is_a (G_OBJECT_TYPE (icon), G_TYPE_THEMED_ICON))
+                    {
+                      const char *type_icon;
 
-	      p = strchr (content_type, '/');
-	      if (p == NULL)
-		p = content_type + strlen (content_type);
+                      /* TODO: Special case desktop dir? That could be expensive with xdg dirs... */
+                      if (strcmp (path, g_get_home_dir ()) == 0)
+                        type_icon = "user-home";
+                      else if (S_ISDIR (statbuf.st_mode)) 
+                        type_icon = "folder";
+                      else if (statbuf.st_mode & S_IXUSR)
+                        type_icon = "application-x-executable";
+                      else
+                        type_icon = "text-x-generic";
 
-	      generic_mimetype_icon = g_malloc (p - content_type + strlen ("-x-generic") + 1);
-	      memcpy (generic_mimetype_icon, content_type, p - content_type);
-	      memcpy (generic_mimetype_icon + (p - content_type), "-x-generic", strlen ("-x-generic"));
-	      generic_mimetype_icon[(p - content_type) + strlen ("-x-generic")] = 0;
+                      g_themed_icon_append_name (G_THEMED_ICON (icon), type_icon);
+                    }
 
-	      /* TODO: Special case desktop dir? That could be expensive with xdg dirs... */
-	      if (strcmp (path, g_get_home_dir ()) == 0)
-		type_icon = "user-home";
-	      else if (S_ISDIR (statbuf.st_mode)) 
-		type_icon = "folder";
-	      else if (statbuf.st_mode & S_IXUSR)
-		type_icon = "application-x-executable";
-	      else
-		type_icon = "text-x-generic";
-
-	      i = 0;
-	      icon_names[i++] = mimetype_icon;
-	      icon_names[i++] = generic_mimetype_icon;
-	      if (strcmp (generic_mimetype_icon, type_icon) != 0 &&
-		  strcmp (mimetype_icon, type_icon) != 0) 
-		icon_names[i++] = type_icon;
-	      
-	      icon = g_themed_icon_new_from_names (icon_names, i);
-	      g_file_info_set_icon (info, icon);
-	      
-	      g_object_unref (icon);
-	      g_free (mimetype_icon);
-	      g_free (generic_mimetype_icon);
+                  g_file_info_set_icon (info, icon);
+                  g_object_unref (icon);
+                }
 	    }
 	  
 	  g_free (content_type);
Index: gio/gthemedicon.c
===================================================================
--- gio/gthemedicon.c	(revision 6545)
+++ gio/gthemedicon.c	(working copy)
@@ -215,6 +215,28 @@
   return (const char * const *)icon->names;
 }
 
+/**
+ * g_themed_icon_append_name:
+ * @icon: a #GThemedIcon
+ * @iconname: name of icon to append to list of icons from within @icon.
+ *
+ * Append a name to the list of icons from within @icon.
+ */
+void
+g_themed_icon_append_name (GThemedIcon *icon, const char *iconname)
+{
+  guint num_names;
+  char **new_names;
+
+  g_return_if_fail (G_IS_THEMED_ICON (icon));
+  g_return_if_fail (iconname != NULL);
+
+  num_names = g_strv_length (icon->names);
+  icon->names = g_realloc (icon->names, sizeof (char*) * (num_names + 2));
+  icon->names[num_names] = g_strdup (iconname);
+  icon->names[num_names + 1] = NULL;
+}
+
 static guint
 g_themed_icon_hash (GIcon *icon)
 {
Index: gio/gthemedicon.h
===================================================================
--- gio/gthemedicon.h	(revision 6545)
+++ gio/gthemedicon.h	(working copy)
@@ -51,6 +51,7 @@
 GIcon *g_themed_icon_new (const char *iconname);
 GIcon *g_themed_icon_new_with_default_fallbacks (const char *iconname);
 GIcon *g_themed_icon_new_from_names (char **iconnames, int len);
+void   g_themed_icon_append_name (GThemedIcon *icon, const char *iconname);
 
 const char * const *g_themed_icon_get_names (GThemedIcon *icon);
 
Index: gio/ChangeLog
===================================================================
--- gio/ChangeLog	(revision 6545)
+++ gio/ChangeLog	(working copy)
@@ -1,3 +1,42 @@
+2008-02-21  David Zeuthen  <davidz redhat com>
+
+	* glocalfileinfo.c: (_g_local_file_info_get):
+	* gcontenttype.c:
+	(g_content_type_get_icon): Implement this function by
+	moving bits from glocalfileinfo.c
+	(g_content_type_get_description): Unalias before getting
+	description (#517687)
+
+	* gfile.c: (g_file_class_init),
+	(g_file_query_filesystem_info_async),
+	(g_file_query_filesystem_info_finish),
+	(query_filesystem_info_data_free),
+	(query_filesystem_info_async_thread),
+	(g_file_real_query_filesystem_info_async),
+	(g_file_real_query_filesystem_info_finish):
+	* gfile.h: Implement async version of
+	g_file_query_filesystem_info()
+
+	* gfileinfo.h: Add new attributes for filesystem::use-preview
+
+	* gio.symbols: Update
+
+	* gthemedicon.c: (g_themed_icon_append_name):
+	* gthemedicon.h: Add new new convenience function.
+
+	* gunionvolumemonitor.c: (g_union_volume_monitor_dispose),
+	(get_mounts), (get_volumes), (get_connected_drives),
+	(get_volume_for_uuid), (get_mount_for_uuid),
+	(g_union_volume_monitor_init), (populate_union_monitor),
+	(g_volume_monitor_get), (_g_mount_get_for_mount_path),
+	(g_volume_monitor_adopt_orphan_mount):
+	* gvolumemonitor.c:
+	* gvolumemonitor.h: Use recursive locks so it's safe for volume
+	monitor implementations to call into the main volume monitor. Also
+	separate object initialization and volume monitor initialization
+	such that non-native volume monitors can properly adopt their
+	mounts away.
+
 2008-02-20  Benjamin Otte  <otte gnome org>
 
 	* gfile.c: (g_file_mount_mountable), (g_file_unmount_mountable),
Index: gio/gfileinfo.h
===================================================================
--- gio/gfileinfo.h	(revision 6545)
+++ gio/gfileinfo.h	(working copy)
@@ -80,6 +80,22 @@
   G_FILE_TYPE_MOUNTABLE
 } GFileType;
 
+/**
+ * GFilesystemPreviewType:
+ * @G_FILESYSTEM_PREVIEW_TYPE_IF_ALWAYS: Only preview files if user has explicitly requested it.
+ * @G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL: Preview files if user has requested preview of "local" files.
+ * @G_FILESYSTEM_PREVIEW_TYPE_NEVER: Never preview files.
+ * 
+ * Indicates a hint from the file system whether files should be
+ * previewed in a file manager. Returned as the value of the key
+ * #G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW.
+ **/
+typedef enum {
+  G_FILESYSTEM_PREVIEW_TYPE_IF_ALWAYS = 0,
+  G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL,
+  G_FILESYSTEM_PREVIEW_TYPE_NEVER
+} GFilesystemPreviewType;
+
 /* Common Attributes:  */
 /**
  * G_FILE_ATTRIBUTE_STANDARD_TYPE:
@@ -680,6 +696,16 @@
 #define G_FILE_ATTRIBUTE_FILESYSTEM_READONLY "filesystem::readonly"               /* boolean */
 
 /**
+ * G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW:
+ *
+ * A key in the "filesystem" namespace for hinting a file manager
+ * application whether it should preview (e.g. thumbnail) files on the
+ * file system. The value for this key contain a
+ * #GFilesystemPreviewType.
+ **/
+#define G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW "filesystem::use-preview"        /* uint32 (GFilesystemPreviewType) */
+
+/**
  * G_FILE_ATTRIBUTE_GVFS_BACKEND:
  * 
  * A key in the "gvfs" namespace that gets the name of the current
Index: gio/gcontenttype.c
===================================================================
--- gio/gcontenttype.c	(revision 6545)
+++ gio/gcontenttype.c	(working copy)
@@ -1,3 +1,5 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
 /* GIO - GLib Input, Output and Streaming Library
  * 
  * Copyright (C) 2006-2007 Red Hat, Inc.
@@ -25,6 +27,7 @@
 #include <string.h>
 #include <stdio.h>
 #include "gcontenttypeprivate.h"
+#include "gthemedicon.h"
 #include "glibintl.h"
 
 #include "gioalias.h"
@@ -591,6 +594,8 @@
   g_return_val_if_fail (type != NULL, NULL);
   
   G_LOCK (gio_xdgmime);
+  type = xdg_mime_unalias_mime_type (type);
+
   if (type_comment_cache == NULL)
     type_comment_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 
@@ -639,10 +644,35 @@
 GIcon *
 g_content_type_get_icon (const char *type)
 {
+  char *mimetype_icon, *generic_mimetype_icon, *p;
+  char *icon_names[2];
+  GThemedIcon *themed_icon;
+  
   g_return_val_if_fail (type != NULL, NULL);
-
-  /* TODO: Implement */
-  return NULL;
+  
+  mimetype_icon = g_strdup (type);
+  
+  while ((p = strchr (mimetype_icon, '/')) != NULL)
+    *p = '-';
+  
+  p = strchr (type, '/');
+  if (p == NULL)
+    p = type + strlen (type);
+  
+  generic_mimetype_icon = g_malloc (p - type + strlen ("-x-generic") + 1);
+  memcpy (generic_mimetype_icon, type, p - type);
+  memcpy (generic_mimetype_icon + (p - type), "-x-generic", strlen ("-x-generic"));
+  generic_mimetype_icon[(p - type) + strlen ("-x-generic")] = 0;
+  
+  icon_names[0] = mimetype_icon;
+  icon_names[1] = generic_mimetype_icon;
+  
+  themed_icon = g_themed_icon_new_from_names (icon_names, 2);
+  
+  g_free (mimetype_icon);
+  g_free (generic_mimetype_icon);
+  
+  return G_ICON (themed_icon);
 }
 
 /**
Index: gio/gvolumemonitor.c
===================================================================
--- gio/gvolumemonitor.c	(revision 6545)
+++ gio/gvolumemonitor.c	(working copy)
@@ -1,3 +1,5 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
 /* GIO - GLib Input, Output and Streaming Library
  * 
  * Copyright (C) 2006-2007 Red Hat, Inc.
Index: gio/gvolumemonitor.h
===================================================================
--- gio/gvolumemonitor.h	(revision 6545)
+++ gio/gvolumemonitor.h	(working copy)
@@ -1,3 +1,5 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
 /* GIO - GLib Input, Output and Streaming Library
  * 
  * Copyright (C) 2006-2007 Red Hat, Inc.
Index: gio/gfile.c
===================================================================
--- gio/gfile.c	(revision 6545)
+++ gio/gfile.c	(working copy)
@@ -1,3 +1,5 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
 /* GIO - GLib Input, Output and Streaming Library
  * 
  * Copyright (C) 2006-2007 Red Hat, Inc.
@@ -100,6 +102,15 @@
 static GFileInfo *        g_file_real_query_info_finish           (GFile                  *file,
 								   GAsyncResult           *res,
 								   GError                **error);
+static void               g_file_real_query_filesystem_info_async (GFile                  *file,
+								   const char             *attributes,
+								   int                     io_priority,
+								   GCancellable           *cancellable,
+								   GAsyncReadyCallback     callback,
+								   gpointer                user_data);
+static GFileInfo *        g_file_real_query_filesystem_info_finish (GFile                  *file,
+								   GAsyncResult           *res,
+								   GError                **error);
 static void               g_file_real_enumerate_children_async    (GFile                  *file,
 								   const char             *attributes,
 								   GFileQueryInfoFlags     flags,
@@ -235,6 +246,8 @@
   iface->set_display_name_finish = g_file_real_set_display_name_finish;
   iface->query_info_async = g_file_real_query_info_async;
   iface->query_info_finish = g_file_real_query_info_finish;
+  iface->query_filesystem_info_async = g_file_real_query_filesystem_info_async;
+  iface->query_filesystem_info_finish = g_file_real_query_filesystem_info_finish;
   iface->set_attributes_async = g_file_real_set_attributes_async;
   iface->set_attributes_finish = g_file_real_set_attributes_finish;
   iface->read_async = g_file_real_read_async;
@@ -1081,6 +1094,81 @@
 }
 
 /**
+ * g_file_query_filesystem_info_async:
+ * @file: input #GFile.
+ * @attributes: an attribute query string.
+ * @io_priority: the <link linkend="io-priority">I/O priority</link> 
+ *     of the request.
+ * @cancellable: optional #GCancellable object, %NULL to ignore. 
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: the data to pass to callback function
+ * 
+ * Asynchronously gets the requested information about the filesystem
+ * that the specified @file is on. The result is a #GFileInfo object
+ * that contains key-value attributes (such as type or size for the
+ * file).
+ * 
+ * For more details, see g_file_query_filesystem_info() which is the
+ * synchronous version of this call.
+ *
+ * When the operation is finished, @callback will be called. You can
+ * then call g_file_query_info_finish() to get the result of the
+ * operation.
+ **/
+void
+g_file_query_filesystem_info_async (GFile               *file,
+                                    const char          *attributes,
+                                    int                  io_priority,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  GFileIface *iface;
+  
+  g_return_if_fail (G_IS_FILE (file));
+
+  iface = G_FILE_GET_IFACE (file);
+  (* iface->query_filesystem_info_async) (file,
+                                          attributes,
+                                          io_priority,
+                                          cancellable,
+                                          callback,
+                                          user_data);
+}
+
+/**
+ * g_file_query_filesystem_info_finish:
+ * @file: input #GFile.
+ * @res: a #GAsyncResult. 
+ * @error: a #GError. 
+ * 
+ * Finishes an asynchronous filesystem info query.  See
+ * g_file_query_filesystem_info_async().
+ * 
+ * Returns: #GFileInfo for given @file or %NULL on error.
+ **/
+GFileInfo *
+g_file_query_filesystem_info_finish (GFile         *file,
+                                     GAsyncResult  *res,
+                                     GError       **error)
+{
+  GFileIface *iface;
+
+  g_return_val_if_fail (G_IS_FILE (file), NULL);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
+
+  if (G_IS_SIMPLE_ASYNC_RESULT (res))
+    {
+      GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+      if (g_simple_async_result_propagate_error (simple, error))
+	return NULL;
+    }
+  
+  iface = G_FILE_GET_IFACE (file);
+  return (* iface->query_filesystem_info_finish) (file, res, error);
+}
+
+/**
  * g_file_find_enclosing_mount:
  * @file: input #GFile.
  * @cancellable: optional #GCancellable object, %NULL to ignore. 
@@ -3695,6 +3783,80 @@
 
 typedef struct {
   char *attributes;
+  GFileInfo *info;
+} QueryFilesystemInfoAsyncData;
+
+static void
+query_filesystem_info_data_free (QueryFilesystemInfoAsyncData *data)
+{
+  if (data->info)
+    g_object_unref (data->info);
+  g_free (data->attributes);
+  g_free (data);
+}
+
+static void
+query_filesystem_info_async_thread (GSimpleAsyncResult *res,
+                                    GObject            *object,
+                                    GCancellable       *cancellable)
+{
+  GError *error = NULL;
+  QueryFilesystemInfoAsyncData *data;
+  GFileInfo *info;
+  
+  data = g_simple_async_result_get_op_res_gpointer (res);
+  
+  info = g_file_query_filesystem_info (G_FILE (object), data->attributes, cancellable, &error);
+
+  if (info == NULL)
+    {
+      g_simple_async_result_set_from_error (res, error);
+      g_error_free (error);
+    }
+  else
+    data->info = info;
+}
+
+static void
+g_file_real_query_filesystem_info_async (GFile               *file,
+                                         const char          *attributes,
+                                         int                  io_priority,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data)
+{
+  GSimpleAsyncResult *res;
+  QueryFilesystemInfoAsyncData *data;
+
+  data = g_new0 (QueryFilesystemInfoAsyncData, 1);
+  data->attributes = g_strdup (attributes);
+  
+  res = g_simple_async_result_new (G_OBJECT (file), callback, user_data, g_file_real_query_filesystem_info_async);
+  g_simple_async_result_set_op_res_gpointer (res, data, (GDestroyNotify)query_filesystem_info_data_free);
+  
+  g_simple_async_result_run_in_thread (res, query_filesystem_info_async_thread, io_priority, cancellable);
+  g_object_unref (res);
+}
+
+static GFileInfo *
+g_file_real_query_filesystem_info_finish (GFile         *file,
+                                          GAsyncResult  *res,
+                                          GError       **error)
+{
+  GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res);
+  QueryFilesystemInfoAsyncData *data;
+
+  g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == g_file_real_query_filesystem_info_async);
+
+  data = g_simple_async_result_get_op_res_gpointer (simple);
+  if (data->info)
+    return g_object_ref (data->info);
+  
+  return NULL;
+}
+
+typedef struct {
+  char *attributes;
   GFileQueryInfoFlags flags;
   GFileEnumerator *enumerator;
 } EnumerateChildrenAsyncData;
Index: gio/gfile.h
===================================================================
--- gio/gfile.h	(revision 6545)
+++ gio/gfile.h	(working copy)
@@ -198,8 +198,8 @@
  * @query_info_async: Asynchronously gets the #GFileInfo for a #GFile.
  * @query_info_finish: Finishes an asynchronous query info operation.
  * @query_filesystem_info: Gets a #GFileInfo for the file system #GFile is on.
- * @_query_filesystem_info_async: Asynchronously gets a #GFileInfo for the file system #GFile is on.
- * @_query_filesystem_info_finish: Finishes asynchronously getting the file system info.
+ * @query_filesystem_info_async: Asynchronously gets a #GFileInfo for the file system #GFile is on.
+ * @query_filesystem_info_finish: Finishes asynchronously getting the file system info.
  * @find_enclosing_mount: Gets a #GMount for the #GFile.
  * @find_enclosing_mount_async: Asynchronously gets the #GMount for a #GFile.
  * @find_enclosing_mount_finish: Finishes asynchronously getting the volume.
@@ -324,8 +324,15 @@
 					     const char           *attributes,
 					     GCancellable         *cancellable,
 					     GError              **error);
-  void                (*_query_filesystem_info_async) (void);
-  void                (*_query_filesystem_info_finish) (void);
+  void                (*query_filesystem_info_async) (GFile                *file,
+                                                      const char           *attributes,
+                                                      int                   io_priority,
+                                                      GCancellable         *cancellable,
+                                                      GAsyncReadyCallback   callback,
+                                                      gpointer              user_data);
+  GFileInfo *         (*query_filesystem_info_finish) (GFile                *file,
+                                                       GAsyncResult         *res,
+                                                       GError              **error);
   
   GMount *            (*find_enclosing_mount)(GFile              *file,
 					       GCancellable       *cancellable,
@@ -658,6 +665,15 @@
 							   const char                 *attributes,
 							   GCancellable               *cancellable,
 							   GError                    **error);
+void                    g_file_query_filesystem_info_async (GFile                      *file,
+							   const char                 *attributes,
+							   int                         io_priority,
+							   GCancellable               *cancellable,
+							   GAsyncReadyCallback         callback,
+							   gpointer                    user_data);
+GFileInfo *             g_file_query_filesystem_info_finish (GFile                      *file,
+                                                           GAsyncResult               *res,
+							   GError                    **error);
 GMount *                g_file_find_enclosing_mount       (GFile                      *file,
                                                            GCancellable               *cancellable,
                                                            GError                    **error);
Index: gio/gunionvolumemonitor.c
===================================================================
--- gio/gunionvolumemonitor.c	(revision 6545)
+++ gio/gunionvolumemonitor.c	(working copy)
@@ -1,3 +1,5 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
 /* GIO - GLib Input, Output and Streaming Library
  * 
  * Copyright (C) 2006-2007 Red Hat, Inc.
@@ -51,8 +53,8 @@
 #define g_union_volume_monitor_get_type _g_union_volume_monitor_get_type
 G_DEFINE_TYPE (GUnionVolumeMonitor, g_union_volume_monitor, G_TYPE_VOLUME_MONITOR);
 
+static GStaticRecMutex the_volume_monitor_mutex = G_STATIC_REC_MUTEX_INIT;
 
-G_LOCK_DEFINE_STATIC(the_volume_monitor);
 static GUnionVolumeMonitor *the_volume_monitor = NULL;
 
 static void
@@ -82,9 +84,9 @@
   
   monitor = G_UNION_VOLUME_MONITOR (object);
 
-  G_LOCK (the_volume_monitor);
+  g_static_rec_mutex_lock (&the_volume_monitor_mutex);
   the_volume_monitor = NULL;
-  G_UNLOCK (the_volume_monitor);
+  g_static_rec_mutex_unlock (&the_volume_monitor_mutex);
   
   if (G_OBJECT_CLASS (g_union_volume_monitor_parent_class)->dispose)
     (*G_OBJECT_CLASS (g_union_volume_monitor_parent_class)->dispose) (object);
@@ -102,7 +104,7 @@
 
   res = NULL;
   
-  G_LOCK (the_volume_monitor);
+  g_static_rec_mutex_lock (&the_volume_monitor_mutex);
 
   for (l = monitor->monitors; l != NULL; l = l->next)
     {
@@ -111,7 +113,7 @@
       res = g_list_concat (res, g_volume_monitor_get_mounts (child_monitor));
     }
   
-  G_UNLOCK (the_volume_monitor);
+  g_static_rec_mutex_unlock (&the_volume_monitor_mutex);
 
   return res;
 }
@@ -128,7 +130,7 @@
 
   res = NULL;
   
-  G_LOCK (the_volume_monitor);
+  g_static_rec_mutex_lock (&the_volume_monitor_mutex);
 
   for (l = monitor->monitors; l != NULL; l = l->next)
     {
@@ -137,7 +139,7 @@
       res = g_list_concat (res, g_volume_monitor_get_volumes (child_monitor));
     }
   
-  G_UNLOCK (the_volume_monitor);
+  g_static_rec_mutex_unlock (&the_volume_monitor_mutex);
 
   return res;
 }
@@ -154,7 +156,7 @@
 
   res = NULL;
   
-  G_LOCK (the_volume_monitor);
+  g_static_rec_mutex_lock (&the_volume_monitor_mutex);
 
   for (l = monitor->monitors; l != NULL; l = l->next)
     {
@@ -163,7 +165,7 @@
       res = g_list_concat (res, g_volume_monitor_get_connected_drives (child_monitor));
     }
   
-  G_UNLOCK (the_volume_monitor);
+  g_static_rec_mutex_unlock (&the_volume_monitor_mutex);
 
   return res;
 }
@@ -180,7 +182,7 @@
 
   volume = NULL;
   
-  G_LOCK (the_volume_monitor);
+  g_static_rec_mutex_lock (&the_volume_monitor_mutex);
 
   for (l = monitor->monitors; l != NULL; l = l->next)
     {
@@ -192,7 +194,7 @@
 
     }
   
-  G_UNLOCK (the_volume_monitor);
+  g_static_rec_mutex_unlock (&the_volume_monitor_mutex);
 
   return volume;
 }
@@ -209,7 +211,7 @@
 
   mount = NULL;
   
-  G_LOCK (the_volume_monitor);
+  g_static_rec_mutex_lock (&the_volume_monitor_mutex);
 
   for (l = monitor->monitors; l != NULL; l = l->next)
     {
@@ -221,7 +223,7 @@
 
     }
   
-  G_UNLOCK (the_volume_monitor);
+  g_static_rec_mutex_unlock (&the_volume_monitor_mutex);
 
   return mount;
 }
@@ -470,6 +472,11 @@
 static void
 g_union_volume_monitor_init (GUnionVolumeMonitor *union_monitor)
 {
+}
+
+static void
+populate_union_monitor (GUnionVolumeMonitor *union_monitor)
+{
   GVolumeMonitor *monitor;
   GNativeVolumeMonitorClass *native_class;
   GVolumeMonitorClass *klass;
@@ -526,17 +533,18 @@
 {
   GVolumeMonitor *vm;
   
-  G_LOCK (the_volume_monitor);
+  g_static_rec_mutex_lock (&the_volume_monitor_mutex);
 
   if (the_volume_monitor)
     vm = G_VOLUME_MONITOR (g_object_ref (the_volume_monitor));
   else
     {
       the_volume_monitor = g_union_volume_monitor_new ();
+      populate_union_monitor (the_volume_monitor);
       vm = G_VOLUME_MONITOR (the_volume_monitor);
     }
   
-  G_UNLOCK (the_volume_monitor);
+  g_static_rec_mutex_unlock (&the_volume_monitor_mutex);
 
   return vm;
 }
@@ -563,9 +571,9 @@
 
   if (klass->get_mount_for_mount_path)
     {
-      G_LOCK (the_volume_monitor);
+      g_static_rec_mutex_lock (&the_volume_monitor_mutex);
       mount = klass->get_mount_for_mount_path (mount_path, cancellable);
-      G_UNLOCK (the_volume_monitor);
+      g_static_rec_mutex_unlock (&the_volume_monitor_mutex);
     }
 
   /* TODO: How do we know this succeeded? Keep in mind that the native
@@ -632,10 +640,8 @@
 
   volume = NULL;
   
-  /* TODO: nasty locking issues because current VM's don't emit signals in idle */
+  g_static_rec_mutex_lock (&the_volume_monitor_mutex);
 
-  /*G_LOCK (the_volume_monitor);*/
-
   for (l = the_volume_monitor->monitors; l != NULL; l = l->next)
     {
       child_monitor = l->data;
@@ -649,7 +655,7 @@
         }
     }
   
-  /*G_UNLOCK (the_volume_monitor);*/
+  g_static_rec_mutex_unlock (&the_volume_monitor_mutex);
 
   return volume;
 }
Index: gio/gio.symbols
===================================================================
--- gio/gio.symbols	(revision 6545)
+++ gio/gio.symbols	(working copy)
@@ -253,6 +253,8 @@
 g_file_query_info_async
 g_file_query_info_finish
 g_file_query_filesystem_info
+g_file_query_filesystem_info_async
+g_file_query_filesystem_info_finish
 g_file_find_enclosing_mount
 g_file_find_enclosing_mount_async
 g_file_find_enclosing_mount_finish
@@ -627,6 +629,7 @@
 g_themed_icon_new_with_default_fallbacks
 g_themed_icon_new_from_names
 g_themed_icon_get_names
+g_themed_icon_append_name
 #endif
 #endif
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]