gvfs r1359 - in trunk: . client daemon hal programs



Author: alexl
Date: Mon Feb 25 11:17:08 2008
New Revision: 1359
URL: http://svn.gnome.org/viewvc/gvfs?rev=1359&view=rev

Log:
2008-02-24  David Zeuthen  <davidz redhat com>

	* client/gdaemonfile.c:
	* client/gdaemonvolumemonitor.c:
	* 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: 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:
	* hal/ghalmount.c:
	* hal/ghalvolume.c:
	* hal/ghalvolumemonitor.c:
	* hal/ghalvolumemonitor.h:
	* hal/hal-pool.c: 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. Also revert the
	commit to ignore NFS mounts as gio will now only report an user
	visible mount if it's in /media or $HOME. Make LUKS encrypted
	volumes work.

	* daemon/Makefile.am:
	* daemon/gphoto2.mount.in:
	* daemon/gvfsbackendgphoto2.c:
	Land the gphoto2 backend.

	* programs/Makefile.am:
	* programs/gvfs-ls.c: Make the --hidden option work. Default
	to short listing and add a --long option. Implement new options
	--show-completions and --show-mounts that can be used for
	shell completions.
	* programs/gvfs-tree.c: New program
	* programs/gvfs-bash-completion.sh: Bash completion for gvfs;
	uses the new --show-completions and --show-mounts options in
	gvfs-ls.




Added:
   trunk/daemon/gphoto2.mount.in
   trunk/daemon/gvfsbackendgphoto2.c
   trunk/daemon/gvfsbackendgphoto2.h
   trunk/programs/gvfs-bash-completion.sh   (contents, props changed)
   trunk/programs/gvfs-tree.c
Modified:
   trunk/ChangeLog
   trunk/client/gdaemonfile.c
   trunk/client/gdaemonvolumemonitor.c
   trunk/client/gdaemonvolumemonitor.h
   trunk/configure.ac
   trunk/daemon/Makefile.am
   trunk/daemon/gvfsbackendcdda.c
   trunk/hal/ghaldrive.c
   trunk/hal/ghalmount.c
   trunk/hal/ghalvolume.c
   trunk/hal/ghalvolumemonitor.c
   trunk/hal/ghalvolumemonitor.h
   trunk/hal/hal-device.c
   trunk/hal/hal-device.h
   trunk/hal/hal-pool.c
   trunk/programs/Makefile.am
   trunk/programs/gvfs-ls.c

Modified: trunk/client/gdaemonfile.c
==============================================================================
--- trunk/client/gdaemonfile.c	(original)
+++ trunk/client/gdaemonfile.c	Mon Feb 25 11:17:08 2008
@@ -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)

Modified: trunk/client/gdaemonvolumemonitor.c
==============================================================================
--- trunk/client/gdaemonvolumemonitor.c	(original)
+++ trunk/client/gdaemonvolumemonitor.c	Mon Feb 25 11:17:08 2008
@@ -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.
@@ -31,6 +33,9 @@
 #include "gdaemonvfs.h"
 #include "gmounttracker.h"
 
+static GStaticRecMutex _the_daemon_volume_monitor_mutex = G_STATIC_REC_MUTEX_INIT;
+static GDaemonVolumeMonitor *_the_daemon_volume_monitor;
+
 struct _GDaemonVolumeMonitor {
   GVolumeMonitor parent;
 
@@ -46,11 +51,15 @@
   GDaemonVolumeMonitor *monitor;
   GList *l;
 
+  g_static_rec_mutex_lock (&_the_daemon_volume_monitor_mutex);
+
   monitor = G_DAEMON_VOLUME_MONITOR (volume_monitor);
 
   l = g_list_copy (monitor->mounts);
   g_list_foreach (l, (GFunc)g_object_ref, NULL);
 
+  g_static_rec_mutex_unlock (&_the_daemon_volume_monitor_mutex);
+
   return l;
 }
 
@@ -103,17 +112,42 @@
   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;
+    }
+
+  g_static_rec_mutex_lock (&_the_daemon_volume_monitor_mutex);
+
+  daemon_mount = find_mount_by_mount_info (_the_daemon_volume_monitor, mount_info);
+  if (daemon_mount != NULL)
+    {
+      g_object_ref (daemon_mount);
+    }
+
+  g_static_rec_mutex_unlock (&_the_daemon_volume_monitor_mutex);
+
+  return daemon_mount;
+}
+
 static void
 mount_added (GDaemonVolumeMonitor *daemon_monitor, GMountInfo *mount_info)
 {
   GDaemonMount *mount;
   GVolume *volume;
 
+  g_static_rec_mutex_lock (&_the_daemon_volume_monitor_mutex);
+
   mount = find_mount_by_mount_info (daemon_monitor, mount_info);
   if (mount)
     {
       g_warning (G_STRLOC ": Mount was added twice!");
-      return;
+      goto out;
     }
 
   if (mount_info->user_visible)
@@ -125,6 +159,9 @@
       daemon_monitor->mounts = g_list_prepend (daemon_monitor->mounts, mount);
       g_signal_emit_by_name (daemon_monitor, "mount_added", mount);
     }
+
+ out:
+  g_static_rec_mutex_unlock (&_the_daemon_volume_monitor_mutex);
 }
 
 static void
@@ -132,18 +169,23 @@
 {
   GDaemonMount *mount;
 
+  g_static_rec_mutex_lock (&_the_daemon_volume_monitor_mutex);
+
   mount = find_mount_by_mount_info (daemon_monitor, mount_info);
   if (!mount)
     {
       if (mount_info->user_visible)
 	g_warning (G_STRLOC ": An unknown mount was removed!");
-      return;
+      goto out;
     }
 
   daemon_monitor->mounts = g_list_remove (daemon_monitor->mounts, mount);
   g_signal_emit_by_name (daemon_monitor, "mount_removed", mount);
   g_signal_emit_by_name (mount, "unmounted");
   g_object_unref (mount);
+
+ out:
+  g_static_rec_mutex_unlock (&_the_daemon_volume_monitor_mutex);
 }
 
 static void
@@ -154,6 +196,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",
@@ -186,6 +230,8 @@
 {
   GDaemonVolumeMonitor *monitor;
   
+  g_static_rec_mutex_lock (&_the_daemon_volume_monitor_mutex);
+
   monitor = G_DAEMON_VOLUME_MONITOR (object);
 
   g_signal_handlers_disconnect_by_func (monitor->mount_tracker, mount_added, monitor);
@@ -198,6 +244,10 @@
   
   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;
+
+  g_static_rec_mutex_unlock (&_the_daemon_volume_monitor_mutex);
 }
 
 static void
@@ -211,7 +261,6 @@
   GVfs *vfs;
   gboolean res;
 
-
   res = FALSE;
 
   /* Don't do anything if the default vfs is not DAEMON_VFS */

Modified: trunk/client/gdaemonvolumemonitor.h
==============================================================================
--- trunk/client/gdaemonvolumemonitor.h	(original)
+++ trunk/client/gdaemonvolumemonitor.h	Mon Feb 25 11:17:08 2008
@@ -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__ */

Modified: trunk/configure.ac
==============================================================================
--- trunk/configure.ac	(original)
+++ trunk/configure.ac	Mon Feb 25 11:17:08 2008
@@ -240,6 +240,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, libgphoto2)
+      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 *******************************
@@ -404,6 +441,7 @@
 	Samba support:	              $msg_samba
 	FUSE support:                 $msg_fuse
         CDDA support:                 $msg_cdda
+        Gphoto2 support:              $msg_gphoto2
         GConf support:                $msg_gconf
         DNS-SD support:               $msg_avahi
 	Use HAL for volume monitor:   $msg_hal (with fast init path: $have_hal_fast_init)

Modified: trunk/daemon/Makefile.am
==============================================================================
--- trunk/daemon/Makefile.am	(original)
+++ trunk/daemon/Makefile.am	Mon Feb 25 11:17:08 2008
@@ -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
@@ -283,10 +289,24 @@
 	-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 \

Added: trunk/daemon/gphoto2.mount.in
==============================================================================
--- (empty file)
+++ trunk/daemon/gphoto2.mount.in	Mon Feb 25 11:17:08 2008
@@ -0,0 +1,4 @@
+[Mount]
+Type=gphoto2
+Exec= libexecdir@/gvfsd-gphoto2
+AutoMount=false

Modified: trunk/daemon/gvfsbackendcdda.c
==============================================================================
--- trunk/daemon/gvfsbackendcdda.c	(original)
+++ trunk/daemon/gvfsbackendcdda.c	Mon Feb 25 11:17:08 2008
@@ -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"
@@ -46,15 +49,15 @@
 #include "gvfsjobqueryinfo.h"
 #include "gvfsjobenumerate.h"
 
+/* see this bug http://bugzilla.gnome.org/show_bug.cgi?id=518284 */
+#define _I18N_LATER(x) x
+
 #define DO_NOT_WANT_PARANOIA_COMPATIBILITY
 #include <cdio/paranoia.h>
 
 /* 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 +89,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 +103,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 +211,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, _I18N_LATER("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, _I18N_LATER("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, _I18N_LATER("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 +264,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 +279,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 +290,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 +365,11 @@
       return;
     }
 
-  if (cdda_backend->drive != NULL)
-    {
-      //g_warning ("closed drive %p", backend);
-      cdio_cddap_close (cdda_backend->drive);
-    }
+  release_device (cdda_backend);
   
-  //g_warning ("unmounted %p", backend);
   g_vfs_job_succeeded (G_VFS_JOB (job));
+
+  //g_warning ("unmounted %p", backend);
 }
 
 /* returns -1 if we couldn't map */
@@ -766,6 +887,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 +929,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;
 }

Added: trunk/daemon/gvfsbackendgphoto2.c
==============================================================================
--- (empty file)
+++ trunk/daemon/gvfsbackendgphoto2.c	Mon Feb 25 11:17:08 2008
@@ -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"
+
+/* see this bug http://bugzilla.gnome.org/show_bug.cgi?id=518284 */
+#define _I18N_LATER(x) x
+
+/*--------------------------------------------------------------------------------------------------------------*/
+
+/* 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)
+    {
+      /* Translator: %s represents the device, e.g. usb:001,042  */
+      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
+                                        {
+                                          if (m == 0)
+                                            /* Translator: %s is the vendor name, e.g. Panasonic */
+                                            name = g_strdup_printf (_("%s Camera"), vendor);
+                                          else
+                                            /* Translator: %s is the vendor name, e.g. Panasonic */
+                                            name = g_strdup_printf (_("%s Audio Player"), vendor);
+                                        }
+                                    }
+                                  libhal_free_property_set (ps2);
+                                }
+                            }
+                          if (name == NULL)
+                            {
+                              if (m == 0)
+                                name = g_strdup (_("Camera"));
+                              else
+                                name = g_strdup (_("Audio Player"));
+                            }
+                          
+                          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, _I18N_LATER("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, _I18N_LATER("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, _I18N_LATER("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, _I18N_LATER("No device 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, _I18N_LATER("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 (_I18N_LATER("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 (_I18N_LATER("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 (_I18N_LATER("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 (_I18N_LATER("Error looking up 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;
+    }
+
+  rc = gp_port_info_list_get_info (il, n, &info);
+  if (rc != 0)
+    {
+      error = get_error_from_gphoto2 (_I18N_LATER("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 (_I18N_LATER("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 (_I18N_LATER("Error initializing camera"), rc);
+      g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+      g_error_free (error);
+      release_device (gphoto2_backend);
+      return;
+    }
+
+  /* Translator: %s represents the device, e.g. usb:001,042  */
+  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, _I18N_LATER("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, 
+                           _I18N_LATER("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 (_I18N_LATER("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 (_I18N_LATER("Error creating file object"), 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 (_I18N_LATER("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 (_I18N_LATER("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,
+			_I18N_LATER("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 (_I18N_LATER("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 (_I18N_LATER("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)
+    {
+      /* application/x-unknown is a bogus mime type return by some
+       * devices (such as Sandisk Sansa music players) - ignore it.
+       */
+      if (strcmp (gp_info.file.type, "application/x-unknown") != 0)
+        {
+          mime_type = g_strdup (gp_info.file.type);
+        }
+    }
+  if (mime_type == NULL)
+    mime_type = g_content_type_guess (name, NULL, 0, NULL);
+  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 (_I18N_LATER("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 (_I18N_LATER("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;
+}

Added: trunk/daemon/gvfsbackendgphoto2.h
==============================================================================
--- (empty file)
+++ trunk/daemon/gvfsbackendgphoto2.h	Mon Feb 25 11:17:08 2008
@@ -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__ */

Modified: trunk/hal/ghaldrive.c
==============================================================================
--- trunk/hal/ghaldrive.c	(original)
+++ trunk/hal/ghaldrive.c	Mon Feb 25 11:17:08 2008
@@ -123,11 +123,18 @@
   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 */

Modified: trunk/hal/ghalmount.c
==============================================================================
--- trunk/hal/ghalmount.c	(original)
+++ trunk/hal/ghalmount.c	Mon Feb 25 11:17:08 2008
@@ -284,8 +284,9 @@
       g_regex_unref (icon_regex);
       g_free (content);
     }
-  
-  if (relative_icon_path)
+
+  /* some autorun.in points to the .exe file for the icon; make sure we avoid using that */
+  if (relative_icon_path && !g_str_has_suffix (relative_icon_path, ".exe"))
     {
       _g_find_file_insensitive_async (data->root,
                                       relative_icon_path,
@@ -322,7 +323,7 @@
   
   m->searched_for_icon = TRUE;
 	
-  search_data = g_new (MountIconSearchData, 1);
+  search_data = g_new0 (MountIconSearchData, 1);
   search_data->mount = g_object_ref (m);
   search_data->root = g_mount_get_root (G_MOUNT (m));
   
@@ -366,10 +367,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 +379,13 @@
   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;
+  gboolean is_crypto;
+  gboolean is_crypto_cleartext;
 
   volume = m->device;
   drive = m->drive_device;
@@ -394,12 +400,44 @@
   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");
+
+  is_crypto = FALSE;
+  is_crypto_cleartext = FALSE;
+  if (strcmp (hal_device_get_property_string (volume, "volume.fsusage"), "crypto") == 0)
+    {
+      is_crypto = TRUE;
+    }
+  if (strlen (hal_device_get_property_string (volume, "volume.crypto_luks.clear.backing_volume")) > 0)
+    {
+      is_crypto_cleartext = TRUE;
+    }
+
   /*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 (is_crypto || is_crypto_cleartext)
+    {
+      icon_name = "media-encrypted";
+    }
+  else if (strcmp (drive_type, "disk") == 0)
     {
       if (strcmp (drive_bus, "ide") == 0)
         icon_name = "drive-harddisk-ata";
@@ -430,7 +468,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)
     {
@@ -613,22 +659,6 @@
   update_from_hal (mount, TRUE);
 }
 
-static gboolean
-should_ignore_non_hal (GUnixMountEntry *mount_entry)
-{
-  const char *fs_type;
-  
-  fs_type = g_unix_mount_get_fs_type (mount_entry);
-
-  /* We don't want to report nfs mounts. They are
-     generally internal things, and cause a lot
-     of pain with autofs and autorun */
-  if (strcmp (fs_type, "nfs") == 0)
-    return TRUE;
-
-  return FALSE;
-}
-
 GHalMount *
 g_hal_mount_new (GVolumeMonitor       *volume_monitor,
                  GUnixMountEntry      *mount_entry,
@@ -641,7 +671,7 @@
   GHalMount *mount;
 
   /* If no volume for mount - Ignore internal things */
-  if (volume == NULL && g_unix_mount_is_system_internal (mount_entry))
+  if (volume == NULL && !g_unix_mount_guess_should_display (mount_entry))
     return NULL;
 
   mount = g_object_new (G_TYPE_HAL_MOUNT, NULL);
@@ -691,7 +721,7 @@
 
  not_hal:
 
-  if (volume != NULL || should_ignore_non_hal (mount_entry))
+  if (volume != NULL)
     {
       g_object_unref (mount);
       return NULL;
@@ -1131,7 +1161,7 @@
   InsensitiveFileSearchData *data;
   GFile *direct_file = g_file_get_child (parent, name);
   
-  data = g_new (InsensitiveFileSearchData, 1);
+  data = g_new0 (InsensitiveFileSearchData, 1);
   data->cancellable = cancellable;
   data->callback = callback;
   data->user_data = user_data;

Modified: trunk/hal/ghalvolume.c
==============================================================================
--- trunk/hal/ghalvolume.c	(original)
+++ trunk/hal/ghalvolume.c	Mon Feb 25 11:17:08 2008
@@ -56,6 +56,7 @@
   GFile *foreign_mount_root;
   GMount *foreign_mount;
   gboolean is_mountable;
+  gboolean ignore_automount;
 
   char *name;
   char *icon;
@@ -202,6 +203,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)
 {
@@ -221,6 +252,8 @@
   HalDevice *drive;
   char *name;
   char *size;
+  gboolean is_crypto;
+  gboolean is_crypto_cleartext;
 
   volume = mv->device;
   drive = mv->drive_device;
@@ -238,6 +271,17 @@
   volume_fsusage = hal_device_get_property_string (volume, "volume.fsusage");
   volume_fstype = hal_device_get_property_string (volume, "volume.fstype");
 
+  is_crypto = FALSE;
+  is_crypto_cleartext = FALSE;
+  if (strcmp (hal_device_get_property_string (volume, "volume.fsusage"), "crypto") == 0)
+    {
+      is_crypto = TRUE;
+    }
+  if (strlen (hal_device_get_property_string (volume, "volume.crypto_luks.clear.backing_volume")) > 0)
+    {
+      is_crypto_cleartext = TRUE;
+    }
+
   if (volume_is_disc && volume_disc_has_audio && mv->foreign_mount_root != NULL)
     name = g_strdup (_("Audio Disc"));
   else
@@ -276,7 +320,11 @@
     }
 
   mv->name = name;
-  mv->icon = _drive_get_icon (drive); /* use the drive icon since we're unmounted */
+
+  if (is_crypto || is_crypto_cleartext)
+    mv->icon = g_strdup ("drive-encrypted");
+  else
+    mv->icon = _drive_get_icon (drive); /* use the drive icon since we're unmounted */
 
   if (hal_device_get_property_bool (volume, "volume.is_mounted"))
     mv->mount_path = g_strdup (hal_device_get_property_string (volume, "volume.mount_point"));
@@ -285,7 +333,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 +344,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");
+
+  is_audio_player = hal_device_has_capability (v->device, "portable_audio_player");
 
-  if (vendor == NULL)
+  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 +436,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 +530,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... 
        */
@@ -469,10 +570,11 @@
   volume->drive_device = g_object_ref (drive_device);
   volume->foreign_mount_root = foreign_mount_root != NULL ? g_object_ref (foreign_mount_root) : NULL;
   volume->is_mountable = is_mountable;
+  volume->ignore_automount = ! hal_device_is_recently_plugged_in (device);
   
   g_signal_connect_object (device, "hal_property_changed", (GCallback) hal_changed, volume, 0);
   g_signal_connect_object (drive_device, "hal_property_changed", (GCallback) hal_changed, volume, 0);
-  
+
   compute_uuid (volume);
   update_from_hal (volume, FALSE);
 
@@ -610,9 +712,7 @@
 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;
+  return ! (hal_volume->ignore_automount);
 }
 
 static GDrive *
@@ -754,6 +854,7 @@
 
   g_simple_async_result_complete (simple);
   g_object_unref (simple);
+  g_object_unref (data->object);
   g_free (data);
 }
 
@@ -769,7 +870,7 @@
   GError *error;
 
   data = g_new0 (SpawnOp, 1);
-  data->object = G_OBJECT (volume);
+  data->object = g_object_ref (volume);
   data->callback = callback;
   data->user_data = user_data;
   data->cancellable = cancellable;
@@ -789,6 +890,7 @@
                                                      data->callback,
                                                      data->user_data,
                                                      error);
+      g_object_unref (data->object);
       g_simple_async_result_complete (simple);
       g_object_unref (simple);
       g_error_free (error);
@@ -902,7 +1004,7 @@
     {
       EjectWrapperOp *data;
       data = g_new0 (EjectWrapperOp, 1);
-      data->object = G_OBJECT (volume);
+      data->object = g_object_ref (volume);
       data->callback = callback;
       data->user_data = user_data;
       g_drive_eject (G_DRIVE (hal_volume->drive), flags, cancellable, eject_wrapper_callback, data);

Modified: trunk/hal/ghalvolumemonitor.c
==============================================================================
--- trunk/hal/ghalvolumemonitor.c	(original)
+++ trunk/hal/ghalvolumemonitor.c	Mon Feb 25 11:17:08 2008
@@ -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)
 {
@@ -693,18 +708,25 @@
     {
       /* no file system on the volume... blank and audio discs are handled in update_discs() */
 
-      /* TODO: davidz: LUKS stuff needs more work; turn off for now */
-#if 0
       /* check if it's a LUKS crypto volume */
       if (strcmp (volume_fsusage, "crypto") == 0)
         {
           if (strcmp (hal_device_get_property_string (d, "volume.fstype"), "crypto_LUKS") == 0)
             {
-              return FALSE;
+              HalDevice *cleartext_device;
+
+              /* avoid showing cryptotext volume if it's corresponding cleartext volume is available */
+              cleartext_device = hal_pool_get_device_by_capability_and_string (pool,
+                                                                               "block",
+                                                                               "volume.crypto_luks.clear.backing_volume",
+                                                                               hal_device_get_udi (d));
+              
+              if (cleartext_device == NULL)
+                {
+                  return FALSE;
+                }
             }
         }
-#endif
-      
       return TRUE;
     }
 
@@ -724,8 +746,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 +762,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;
@@ -879,7 +907,12 @@
           drive = find_drive_by_udi (monitor, hal_device_get_property_string (d, "block.storage_device"));
           
           /*g_warning ("hal adding vol %s (drive %p)", hal_device_get_property_string (d, "block.device"), drive);*/
-          volume = g_hal_volume_new (G_VOLUME_MONITOR (monitor), d, monitor->pool, NULL, TRUE, drive);
+          volume = g_hal_volume_new (G_VOLUME_MONITOR (monitor), 
+                                     d, 
+                                     monitor->pool, 
+                                     NULL, 
+                                     TRUE, 
+                                     drive);
           if (volume != NULL)
             {
               monitor->volumes = g_list_prepend (monitor->volumes, volume);
@@ -1038,7 +1071,11 @@
           mount = NULL;
           if (hal_device_get_property_bool (d, "volume.disc.is_blank"))
             {
-              volume = g_hal_volume_new (G_VOLUME_MONITOR (monitor), d, monitor->pool, NULL, FALSE, drive);
+              volume = g_hal_volume_new (G_VOLUME_MONITOR (monitor), 
+                                         d, monitor->pool, 
+                                         NULL, 
+                                         FALSE, 
+                                         drive);
               if (volume != NULL)
                 {
                   GFile *root;
@@ -1067,7 +1104,12 @@
               g_free (device_basename);
               g_free (uri);
 
-              volume = g_hal_volume_new (G_VOLUME_MONITOR (monitor), d, monitor->pool, foreign_mount_root, TRUE, drive);
+              volume = g_hal_volume_new (G_VOLUME_MONITOR (monitor), 
+                                         d, 
+                                         monitor->pool, 
+                                         foreign_mount_root, 
+                                         TRUE, 
+                                         drive);
               g_object_unref (foreign_mount_root);
               mount = NULL;
             }
@@ -1096,14 +1138,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 +1209,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);
@@ -1160,7 +1217,12 @@
       udi = hal_device_get_udi (d);
       /*g_warning ("camera adding %s", udi);*/
 
-      volume = g_hal_volume_new (G_VOLUME_MONITOR (monitor), d, monitor->pool, foreign_mount_root, TRUE, NULL);
+      volume = g_hal_volume_new (G_VOLUME_MONITOR (monitor), 
+                                 d, 
+                                 monitor->pool, 
+                                 foreign_mount_root, 
+                                 TRUE, 
+                                 NULL);
       g_object_unref (foreign_mount_root);
       if (volume != NULL)
         {

Modified: trunk/hal/ghalvolumemonitor.h
==============================================================================
--- trunk/hal/ghalvolumemonitor.h	(original)
+++ trunk/hal/ghalvolumemonitor.h	Mon Feb 25 11:17:08 2008
@@ -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 ())

Modified: trunk/hal/hal-device.c
==============================================================================
--- trunk/hal/hal-device.c	(original)
+++ trunk/hal/hal-device.c	Mon Feb 25 11:17:08 2008
@@ -30,6 +30,7 @@
   LibHalContext *hal_ctx;
   LibHalPropertySet *properties;
   char *udi;
+  GTimeVal time_added;
 };
 
 enum {
@@ -91,6 +92,7 @@
 hal_device_init (HalDevice *device)
 {
   device->priv = g_new0 (HalDevicePrivate, 1);
+  g_get_current_time (&(device->priv->time_added));
 }
 
 const char *
@@ -292,3 +294,17 @@
 {
   hal_device_register_type (G_TYPE_MODULE (module));
 }
+
+gboolean
+hal_device_is_recently_plugged_in (HalDevice *device)
+{
+  GTimeVal now;
+  glong delta_msec;
+
+  g_get_current_time (&now);
+
+  delta_msec = (now.tv_sec - device->priv->time_added.tv_sec) * 1000  + 
+    (now.tv_usec - device->priv->time_added.tv_usec) / 1000;
+
+  return delta_msec < 2000;
+}

Modified: trunk/hal/hal-device.h
==============================================================================
--- trunk/hal/hal-device.h	(original)
+++ trunk/hal/hal-device.h	Mon Feb 25 11:17:08 2008
@@ -91,4 +91,6 @@
 gboolean            hal_device_has_interface               (HalDevice         *device,
                                                             const char        *interface);
 
+gboolean            hal_device_is_recently_plugged_in      (HalDevice         *device);
+
 #endif /* HAL_DEVICE_H */

Modified: trunk/hal/hal-pool.c
==============================================================================
--- trunk/hal/hal-pool.c	(original)
+++ trunk/hal/hal-pool.c	Mon Feb 25 11:17:08 2008
@@ -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;

Modified: trunk/programs/Makefile.am
==============================================================================
--- trunk/programs/Makefile.am	(original)
+++ trunk/programs/Makefile.am	Mon Feb 25 11:17:08 2008
@@ -16,6 +16,7 @@
 	gvfs-open				\
 	gvfs-save				\
 	gvfs-ls					\
+	gvfs-tree				\
 	gvfs-info				\
 	gvfs-trash				\
 	gvfs-rm					\
@@ -30,6 +31,9 @@
 	gvfs-less				\
 	$(NULL)
 
+profiledir = $(sysconfdir)/profile.d
+profile_SCRIPTS = gvfs-bash-completion.sh
+
 gvfs_cat_SOURCES = gvfs-cat.c
 gvfs_cat_LDADD = $(libraries)
 
@@ -54,6 +58,9 @@
 gvfs_ls_SOURCES = gvfs-ls.c
 gvfs_ls_LDADD = $(libraries)
 
+gvfs_tree_SOURCES = gvfs-tree.c
+gvfs_tree_LDADD = $(libraries)
+
 gvfs_move_SOURCES = gvfs-move.c
 gvfs_move_LDADD = $(libraries)
 

Added: trunk/programs/gvfs-bash-completion.sh
==============================================================================
--- (empty file)
+++ trunk/programs/gvfs-bash-completion.sh	Mon Feb 25 11:17:08 2008
@@ -0,0 +1,56 @@
+#!/bin/sh
+
+# 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>
+
+# Check for bash                                                                
+[ -z "$BASH_VERSION" ] && return
+
+####################################################################################################
+
+# don't misbehave on colons; See item E13 at http://tiswww.case.edu/php/chet/bash/FAQ
+COMP_WORDBREAKS=${COMP_WORDBREAKS//:}
+
+__gvfs_multiple_uris() {
+    local IFS=$'\n'
+    local cur="${COMP_WORDS[COMP_CWORD]}"
+
+    if [ "$cur" = "" ] ; then
+        COMPREPLY=($(compgen -W "$(gvfs-ls --show-mounts)" -- $cur))
+    else
+        COMPREPLY=($(compgen -W "$(gvfs-ls --show-completions $cur)" -- $cur))
+    fi
+}
+
+####################################################################################################
+
+complete -o nospace -F __gvfs_multiple_uris gvfs-ls
+complete -o nospace -F __gvfs_multiple_uris gvfs-info
+complete -o nospace -F __gvfs_multiple_uris gvfs-cat
+complete -o nospace -F __gvfs_multiple_uris gvfs-less
+complete -o nospace -F __gvfs_multiple_uris gvfs-copy
+complete -o nospace -F __gvfs_multiple_uris gvfs-mkdir
+complete -o nospace -F __gvfs_multiple_uris gvfs-monitor-dir
+complete -o nospace -F __gvfs_multiple_uris gvfs-monitor-file
+complete -o nospace -F __gvfs_multiple_uris gvfs-move
+complete -o nospace -F __gvfs_multiple_uris gvfs-open
+complete -o nospace -F __gvfs_multiple_uris gvfs-rm
+complete -o nospace -F __gvfs_multiple_uris gvfs-save
+complete -o nospace -F __gvfs_multiple_uris gvfs-trash
+complete -o nospace -F __gvfs_multiple_uris gvfs-tree

Modified: trunk/programs/gvfs-ls.c
==============================================================================
--- trunk/programs/gvfs-ls.c	(original)
+++ trunk/programs/gvfs-ls.c	Mon Feb 25 11:17:08 2008
@@ -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.
@@ -29,11 +31,17 @@
 
 static char *attributes = NULL;
 static gboolean show_hidden = FALSE;
+static gboolean show_mounts = FALSE;
+static gboolean show_long = FALSE;
+static char *show_completions = NULL;
 
 static GOptionEntry entries[] = 
 {
 	{ "attributes", 'a', 0, G_OPTION_ARG_STRING, &attributes, "The attributes to get", NULL },
 	{ "hidden", 'h', 0, G_OPTION_ARG_NONE, &show_hidden, "Show hidden files", NULL },
+        { "long", 'l', 0, G_OPTION_ARG_NONE, &show_long, "Use a long listing format", NULL },
+        { "show-completions", 'c', 0, G_OPTION_ARG_STRING, &show_completions, "Show completions", NULL}, 
+        { "show-mounts", 'm', 0, G_OPTION_ARG_NONE, &show_mounts, "Show mounts", NULL },
 	{ NULL }
 };
 
@@ -86,7 +94,10 @@
 
   size = g_file_info_get_size (info);
   type = type_to_string (g_file_info_get_file_type (info));
-  g_print ("%s\t%"G_GUINT64_FORMAT"\t(%s)", name, (guint64)size, type);
+  if (show_long)
+    g_print ("%s\t%"G_GUINT64_FORMAT"\t(%s)", name, (guint64)size, type);
+  else
+    g_print ("%s", name);
 
   first_attr = TRUE;
   attributes = g_file_info_list_attributes (info, NULL);
@@ -94,9 +105,11 @@
     {
       char *val_as_string;
 
-      if (strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_NAME) == 0 ||
+      if (!show_long ||
+          strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_NAME) == 0 ||
 	  strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_SIZE) == 0 ||
-	  strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_TYPE) == 0)
+	  strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_TYPE) == 0 ||
+	  strcmp (attributes[i], G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) == 0)
 	continue;
 
       if (first_attr)
@@ -130,7 +143,7 @@
   enumerator = g_file_enumerate_children (file, attributes, 0, NULL, &error);
   if (enumerator == NULL)
     {
-      g_print ("Error: %s\n", error->message);
+      g_printerr ("Error: %s\n", error->message);
       g_error_free (error);
       error = NULL;
       return;
@@ -145,19 +158,157 @@
 
   if (error)
     {
-      g_print ("Error: %s\n", error->message);
+      g_printerr ("Error: %s\n", error->message);
       g_error_free (error);
       error = NULL;
     }
 	 
   if (!g_file_enumerator_close (enumerator, NULL, &error))
     {
-      g_print ("Error closing enumerator: %s\n", error->message);
+      g_printerr ("Error closing enumerator: %s\n", error->message);
       g_error_free (error);
       error = NULL;
     }
 }
 
+static void
+print_mounts (const char *prefix)
+{
+  GList *l;
+  GList *mounts;
+  GVolumeMonitor *volume_monitor;
+
+  volume_monitor = g_volume_monitor_get ();
+  
+  mounts = g_volume_monitor_get_mounts (volume_monitor);
+  if (mounts != NULL)
+    {
+      for (l = mounts; l != NULL; l = l->next)
+        {
+          GMount *mount = l->data;
+          GFile *mount_root;
+          char *uri;
+          
+          mount_root = g_mount_get_root (mount);
+          uri = g_file_get_uri (mount_root);
+          if (prefix == NULL ||
+              g_str_has_prefix (uri, prefix))
+            g_print ("%s\n", uri);
+          g_free (uri);
+          g_object_unref (mount_root);
+          g_object_unref (mount);
+        }
+      g_list_free (mounts);
+    }
+  g_object_unref (volume_monitor);
+
+  if (prefix == NULL || g_str_has_prefix ("file:///", prefix))
+    g_print ("file:///\n");
+}
+
+static void
+show_completed_file (GFile *hit,
+                     gboolean is_dir,
+                     const char *arg)
+{
+  char *display, *cwd;
+  GFile *cwd_f;
+  
+  if (g_file_is_native (hit))
+    {
+      cwd = g_get_current_dir ();
+      cwd_f = g_file_new_for_path (cwd);
+      g_free (cwd);
+
+      if (g_file_has_prefix (hit, cwd_f) &&
+          !g_path_is_absolute (arg))
+        display = g_file_get_relative_path (cwd_f, hit);
+      else
+        display = g_file_get_path (hit);
+      g_object_unref (cwd_f);
+    }
+  else
+    display = g_file_get_uri (hit);
+  
+  g_print ("%s%s\n", display, (is_dir)?"/":"");
+  g_free (display);
+}
+  
+static void
+print_completions (const char *arg)
+{
+  GFile *f;
+  GFile *parent;
+  char *basename;
+
+  f = g_file_new_for_commandline_arg (arg);
+  
+  if (g_str_has_suffix (arg, "/") ||
+      *arg == 0)
+    {
+      parent = g_object_ref (f);
+      basename = g_strdup ("");
+    }
+  else
+    {
+      parent = g_file_get_parent (f);
+      basename = g_file_get_basename (f);
+    }
+
+  if (parent == NULL ||
+      !g_file_query_exists (parent, NULL))
+    {
+      GMount *mount;
+      mount = g_file_find_enclosing_mount (f, NULL, NULL);
+      if (mount == NULL)
+        {
+          print_mounts (arg);
+          goto out;
+        }
+      g_object_unref (mount);
+    }
+  
+  if (parent != NULL)
+    {
+      GFileEnumerator *enumerator;
+      enumerator = g_file_enumerate_children (parent, 
+                                              G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                              G_FILE_ATTRIBUTE_STANDARD_TYPE,
+                                              0, 
+                                              NULL, 
+                                              NULL);
+      if (enumerator != NULL)
+        {
+          GFileInfo *info;
+          
+          while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL)
+            {
+              const char *name;
+              GFileType type;
+              
+              name = g_file_info_get_name (info);
+              type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE);
+              if (name != NULL && g_str_has_prefix (name, basename))
+                {
+                  GFile *entry;
+                  char *entry_uri;
+                  
+                  entry = g_file_get_child (parent, name);
+                  show_completed_file (entry, type == G_FILE_TYPE_DIRECTORY, arg);
+                  g_object_unref (entry);
+                }
+              g_object_unref (info);
+            }
+          g_file_enumerator_close (enumerator, NULL, NULL);
+        }
+      g_object_unref (parent);
+    }
+
+ out:
+  g_object_unref (f);
+  g_free (basename);
+}
+
 int
 main (int argc, char *argv[])
 {
@@ -175,12 +326,30 @@
   g_option_context_parse (context, &argc, &argv, &error);
   g_option_context_free (context);
 
+  if (attributes != NULL)
+    {
+      /* asking for attributes implies -l; otherwise it won't get shown */
+      show_long = TRUE;
+    }
+
   attributes = g_strconcat (G_FILE_ATTRIBUTE_STANDARD_NAME ","
 			    G_FILE_ATTRIBUTE_STANDARD_TYPE ","
-			    G_FILE_ATTRIBUTE_STANDARD_SIZE,
+			    G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+			    G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN,
 			    attributes != NULL ? "," : "",
 			    attributes,
 			    NULL);
+
+  if (show_mounts)
+    {
+      print_mounts (NULL);
+      return 0;
+    }
+  else if (show_completions != NULL)
+    {
+      print_completions (show_completions);
+      return 0;
+    }
   
   if (argc > 1)
     {

Added: trunk/programs/gvfs-tree.c
==============================================================================
--- (empty file)
+++ trunk/programs/gvfs-tree.c	Mon Feb 25 11:17:08 2008
@@ -0,0 +1,270 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* 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>
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <locale.h>
+#include <string.h>
+#include <gio/gio.h>
+
+static gboolean show_hidden = FALSE;
+static gboolean follow_symlinks = FALSE;
+
+static GOptionEntry entries[] = 
+{
+	{ "hidden", 'h', 0, G_OPTION_ARG_NONE, &show_hidden, "Show hidden files", NULL },
+	{ "follow-symlinks", 'l', 0, G_OPTION_ARG_NONE, &follow_symlinks, "Follow symlinks, mounts and shortcuts like dirs", NULL },
+};
+
+static gint
+sort_info_by_name (GFileInfo *a, GFileInfo *b)
+{
+  const char *na;
+  const char *nb;
+
+  na = g_file_info_get_name (a);
+  nb = g_file_info_get_name (b);
+
+  if (na == NULL)
+    na = "";
+  if (nb == NULL)
+    nb = "";
+
+  return strcmp (na, nb);
+}
+
+static void
+do_tree (GFile *f, int level, guint64 pattern)
+{
+  GFileEnumerator *enumerator;
+  GError *error = NULL;
+  unsigned int n;
+  GFileInfo *info;
+
+  info = g_file_query_info (f, 
+                            G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+                            G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
+                            0, 
+                            NULL, NULL);
+  if (info != NULL)
+    {
+      if (g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_MOUNTABLE)
+        {
+          /* don't process mountables; we avoid these by getting the target_uri below */
+          g_object_unref (info);
+          return;
+        }
+      g_object_unref (info);
+    }
+
+  enumerator = g_file_enumerate_children (f, 
+                                          G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                          G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+                                          G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
+                                          G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK ","
+                                          G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET ","
+                                          G_FILE_ATTRIBUTE_STANDARD_TARGET_URI,
+                                          0, 
+                                          NULL, 
+                                          &error);
+  if (enumerator != NULL)
+    {
+      GList *l;
+      GList *info_list;
+
+      info_list = NULL;
+      while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL)
+        {
+          if (g_file_info_get_is_hidden (info) && !show_hidden)
+            {
+              g_object_unref (info);
+            }
+          else
+            {
+              info_list = g_list_prepend (info_list, info);
+            }
+        }
+      g_file_enumerator_close (enumerator, NULL, NULL);
+
+      info_list = g_list_sort (info_list, (GCompareFunc) sort_info_by_name);
+
+      for (l = info_list; l != NULL; l = l->next)
+        {
+          const char *name;
+          const char *target_uri;
+          GFileType type;
+          gboolean is_last_item;
+
+          info = l->data;
+          is_last_item = (l->next == NULL);
+
+          name = g_file_info_get_name (info);
+          type = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE);
+          if (name != NULL)
+            {
+
+              for (n = 0; n < level; n++)
+                {
+                  if (pattern & (1<<n))
+                    {
+                      g_print ("|   ");
+                    }
+                  else
+                    {
+                      g_print ("    ");
+                    }
+                }
+
+              if (is_last_item)
+                {
+                  g_print ("`-- %s", name);
+                }
+              else
+                {
+                  g_print ("|-- %s", name);
+                }
+
+              target_uri = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI);
+              if (target_uri != NULL)
+                {
+                  g_print (" -> %s", target_uri);
+                }
+              else
+                {
+                  if (g_file_info_get_is_symlink (info))
+                    {
+                      const char *target;
+                      target = g_file_info_get_symlink_target (info);
+                      g_print (" -> %s", target);
+                    }
+                }
+
+              g_print ("\n");
+
+              if ((type & G_FILE_TYPE_DIRECTORY) && 
+                  (follow_symlinks || !g_file_info_get_is_symlink (info)))
+                {
+                  guint64 new_pattern;
+                  GFile *child;
+
+                  if (is_last_item)
+                    new_pattern = pattern;
+                  else
+                    new_pattern = pattern | (1<<level);
+
+                  child = NULL;
+                  if (target_uri != NULL)
+                    {
+                      if (follow_symlinks)
+                        child = g_file_new_for_uri (target_uri);
+                    }
+                  else
+                    {
+                      child = g_file_get_child (f, name);
+                    }
+
+                  if (child != NULL)
+                    {
+                      do_tree (child, level + 1, new_pattern);
+                      g_object_unref (child);
+                    }
+                }
+            }
+          g_object_unref (info);
+        }
+      g_list_free (info_list);
+    }
+  else
+    {
+      for (n = 0; n < level; n++)
+        {
+          if (pattern & (1<<n))
+            {
+              g_print ("|   ");
+            }
+          else
+            {
+              g_print ("    ");
+            }
+        }
+
+      g_print ("    [%s]\n", error->message);
+
+      g_error_free (error);
+    }
+}
+
+static void
+tree (GFile *f)
+{
+  char *uri;
+
+  uri = g_file_get_uri (f);
+  g_print ("%s\n", uri);
+  g_free (uri);
+
+  do_tree (f, 0, 0);
+}
+
+int
+main (int argc, char *argv[])
+{
+  GError *error;
+  GOptionContext *context;
+  GFile *file;
+  
+  setlocale (LC_ALL, "");
+
+  g_type_init ();
+  
+  error = NULL;
+  context = g_option_context_new ("- list contents of directories in a tree-like format");
+  g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
+  g_option_context_parse (context, &argc, &argv, &error);
+  g_option_context_free (context);
+
+  if (argc > 1)
+    {
+      int i;
+      
+      for (i = 1; i < argc; i++) {
+	file = g_file_new_for_commandline_arg (argv[i]);
+	tree (file);
+	g_object_unref (file);
+      }
+    }
+  else
+    {
+      char *cwd;
+      
+      cwd = g_get_current_dir ();
+      file = g_file_new_for_path (cwd);
+      g_free (cwd);
+      tree (file);
+      g_object_unref (file);
+    }
+
+  return 0;
+}



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