summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Zeuthen <davidz@redhat.com>2008-02-25 11:17:08 +0000
committerAlexander Larsson <alexl@src.gnome.org>2008-02-25 11:17:08 +0000
commitfd09daa489fbf2158472c4a7f3b4e92bf8f4ec0c (patch)
treeacfdb20f29bf00584ade92c855d65c7dec241264
parent08ef6a83f785d5ac1eb423f8253c6455ad596232 (diff)
downloadgvfs-fd09daa489fbf2158472c4a7f3b4e92bf8f4ec0c.tar.gz
Prefer to return a GDaemonMount from an existing GDaemonVolumeMonitor
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. svn path=/trunk/; revision=1359
-rw-r--r--ChangeLog47
-rw-r--r--client/gdaemonfile.c7
-rw-r--r--client/gdaemonvolumemonitor.c55
-rw-r--r--client/gdaemonvolumemonitor.h3
-rw-r--r--configure.ac38
-rw-r--r--daemon/Makefile.am24
-rw-r--r--daemon/gphoto2.mount.in4
-rw-r--r--daemon/gvfsbackendcdda.c171
-rw-r--r--daemon/gvfsbackendgphoto2.c1405
-rw-r--r--daemon/gvfsbackendgphoto2.h51
-rw-r--r--hal/ghaldrive.c47
-rw-r--r--hal/ghalmount.c86
-rw-r--r--hal/ghalvolume.c140
-rw-r--r--hal/ghalvolumemonitor.c96
-rw-r--r--hal/ghalvolumemonitor.h5
-rw-r--r--hal/hal-device.c16
-rw-r--r--hal/hal-device.h2
-rw-r--r--hal/hal-pool.c6
-rw-r--r--programs/Makefile.am7
-rwxr-xr-xprograms/gvfs-bash-completion.sh56
-rw-r--r--programs/gvfs-ls.c183
-rw-r--r--programs/gvfs-tree.c270
22 files changed, 2612 insertions, 107 deletions
diff --git a/ChangeLog b/ChangeLog
index 5221b82a..137e7b11 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,50 @@
+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.
+
2008-02-25 Alexander Larsson <alexl@redhat.com>
* daemon/gvfsbackendnetwork.c:
diff --git a/client/gdaemonfile.c b/client/gdaemonfile.c
index 65c01f66..29808a91 100644
--- a/client/gdaemonfile.c
+++ b/client/gdaemonfile.c
@@ -1530,7 +1530,12 @@ g_daemon_file_find_enclosing_mount (GFile *file,
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)
diff --git a/client/gdaemonvolumemonitor.c b/client/gdaemonvolumemonitor.c
index 0efd0464..8179031e 100644
--- a/client/gdaemonvolumemonitor.c
+++ b/client/gdaemonvolumemonitor.c
@@ -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 @@ get_mounts (GVolumeMonitor *volume_monitor)
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 @@ find_mount_by_mount_info (GDaemonVolumeMonitor *daemon_monitor, GMountInfo *moun
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 @@ mount_added (GDaemonVolumeMonitor *daemon_monitor, GMountInfo *mount_info)
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 @@ mount_removed (GDaemonVolumeMonitor *daemon_monitor, GMountInfo *mount_info)
{
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 @@ g_daemon_volume_monitor_init (GDaemonVolumeMonitor *daemon_monitor)
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 @@ g_daemon_volume_monitor_finalize (GObject *object)
{
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 @@ g_daemon_volume_monitor_finalize (GObject *object)
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 @@ is_supported (void)
GVfs *vfs;
gboolean res;
-
res = FALSE;
/* Don't do anything if the default vfs is not DAEMON_VFS */
diff --git a/client/gdaemonvolumemonitor.h b/client/gdaemonvolumemonitor.h
index 2e3e3014..4019ca35 100644
--- a/client/gdaemonvolumemonitor.h
+++ b/client/gdaemonvolumemonitor.h
@@ -25,6 +25,7 @@
#include <glib-object.h>
#include <gio/gio.h>
+#include "gmounttracker.h"
G_BEGIN_DECLS
@@ -52,6 +53,8 @@ void g_daemon_volume_monitor_register_types (GTypeModule *type_module);
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__ */
diff --git a/configure.ac b/configure.ac
index b60a5a3d..031dcf92 100644
--- a/configure.ac
+++ b/configure.ac
@@ -240,6 +240,43 @@ AC_SUBST(HAL_CFLAGS)
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 @@ echo "
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)
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 05f0f18c..689cd9c0 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -57,6 +57,12 @@ mount_DATA += cdda.mount
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 @@ gvfsd_cdda_CPPFLAGS = \
-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 \
diff --git a/daemon/gphoto2.mount.in b/daemon/gphoto2.mount.in
new file mode 100644
index 00000000..2602afa3
--- /dev/null
+++ b/daemon/gphoto2.mount.in
@@ -0,0 +1,4 @@
+[Mount]
+Type=gphoto2
+Exec=@libexecdir@/gvfsd-gphoto2
+AutoMount=false
diff --git a/daemon/gvfsbackendcdda.c b/daemon/gvfsbackendcdda.c
index 29ea9dd8..8ce35551 100644
--- a/daemon/gvfsbackendcdda.c
+++ b/daemon/gvfsbackendcdda.c
@@ -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 @@ struct _GVfsBackendCdda
{
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 @@ struct _GVfsBackendCdda
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 @@ do_mount (GVfsBackend *backend,
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 @@ do_mount (GVfsBackend *backend,
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 @@ do_mount (GVfsBackend *backend,
_("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 @@ do_mount (GVfsBackend *backend,
_("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 @@ do_unmount (GVfsBackend *backend,
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 @@ do_enumerate (GVfsBackend *backend,
}
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 @@ g_vfs_backend_cdda_class_init (GVfsBackendCddaClass *klass)
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;
}
diff --git a/daemon/gvfsbackendgphoto2.c b/daemon/gvfsbackendgphoto2.c
new file mode 100644
index 00000000..ae5cb4fa
--- /dev/null
+++ b/daemon/gvfsbackendgphoto2.c
@@ -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;
+}
diff --git a/daemon/gvfsbackendgphoto2.h b/daemon/gvfsbackendgphoto2.h
new file mode 100644
index 00000000..33ad88a5
--- /dev/null
+++ b/daemon/gvfsbackendgphoto2.h
@@ -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__ */
diff --git a/hal/ghaldrive.c b/hal/ghaldrive.c
index f0926b8a..f2259c2b 100644
--- a/hal/ghaldrive.c
+++ b/hal/ghaldrive.c
@@ -123,11 +123,18 @@ _drive_get_description (HalDevice *d)
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 @@ _drive_get_icon (HalDevice *d)
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 */
diff --git a/hal/ghalmount.c b/hal/ghalmount.c
index 60e834b6..a3e700de 100644
--- a/hal/ghalmount.c
+++ b/hal/ghalmount.c
@@ -284,8 +284,9 @@ on_autorun_loaded (GObject *source_object, GAsyncResult *res,
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 @@ _g_find_mount_icon (GHalMount *m)
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 @@ do_update_from_hal (GHalMount *m)
{
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 @@ do_update_from_hal (GHalMount *m)
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 @@ do_update_from_hal (GHalMount *m)
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 @@ do_update_from_hal (GHalMount *m)
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 @@ g_hal_mount_override_icon (GHalMount *mount, GIcon *icon)
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 @@ g_hal_mount_new (GVolumeMonitor *volume_monitor,
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 @@ g_hal_mount_new (GVolumeMonitor *volume_monitor,
not_hal:
- if (volume != NULL || should_ignore_non_hal (mount_entry))
+ if (volume != NULL)
{
g_object_unref (mount);
return NULL;
@@ -1131,7 +1161,7 @@ _g_find_file_insensitive_async (GFile *parent,
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;
diff --git a/hal/ghalvolume.c b/hal/ghalvolume.c
index a17f21b2..c0a8b615 100644
--- a/hal/ghalvolume.c
+++ b/hal/ghalvolume.c
@@ -56,6 +56,7 @@ struct _GHalVolume {
GFile *foreign_mount_root;
GMount *foreign_mount;
gboolean is_mountable;
+ gboolean ignore_automount;
char *name;
char *icon;
@@ -202,6 +203,36 @@ format_size_for_display (guint64 size)
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 @@ do_update_from_hal (GHalVolume *mv)
HalDevice *drive;
char *name;
char *size;
+ gboolean is_crypto;
+ gboolean is_crypto_cleartext;
volume = mv->device;
drive = mv->drive_device;
@@ -238,6 +271,17 @@ do_update_from_hal (GHalVolume *mv)
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 @@ do_update_from_hal (GHalVolume *mv)
}
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 @@ do_update_from_hal (GHalVolume *mv)
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 @@ do_update_from_hal (GHalVolume *mv)
(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 @@ update_from_hal (GHalVolume *mv, gboolean emit_changed)
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 @@ g_hal_volume_new (GVolumeMonitor *volume_monitor,
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 @@ g_hal_volume_new (GVolumeMonitor *volume_monitor,
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 @@ static gboolean
g_hal_volume_should_automount (GVolume *volume)
{
GHalVolume *hal_volume = G_HAL_VOLUME (volume);
- /* TODO: For now, just never automount things that are not
- local. Need to figure out a better approach later. */
- return hal_volume->foreign_mount == NULL;
+ return ! (hal_volume->ignore_automount);
}
static GDrive *
@@ -754,6 +854,7 @@ spawn_cb (GPid pid, gint status, gpointer user_data)
g_simple_async_result_complete (simple);
g_object_unref (simple);
+ g_object_unref (data->object);
g_free (data);
}
@@ -769,7 +870,7 @@ spawn_do (GVolume *volume,
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 @@ spawn_do (GVolume *volume,
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 @@ g_hal_volume_eject (GVolume *volume,
{
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);
diff --git a/hal/ghalvolumemonitor.c b/hal/ghalvolumemonitor.c
index dc8316e7..d0e99f5b 100644
--- a/hal/ghalvolumemonitor.c
+++ b/hal/ghalvolumemonitor.c
@@ -93,7 +93,7 @@ G_DEFINE_DYNAMIC_TYPE (GHalVolumeMonitor, g_hal_volume_monitor, G_TYPE_NATIVE_VO
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 @@ adopt_orphan_mount (GMount *mount)
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 @@ adopt_orphan_mount (GMount *mount)
{
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 @@ find_disc_volume_by_udi (GHalVolumeMonitor *monitor, const char *udi)
return NULL;
}
-#ifdef _WITH_GPHOTO2
+#ifdef HAVE_GPHOTO2
static GHalVolume *
find_camera_volume_by_udi (GHalVolumeMonitor *monitor, const char *udi)
{
@@ -693,18 +708,25 @@ should_volume_be_ignored (HalPool *pool, HalDevice *d)
{
/* 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 @@ should_drive_be_ignored (HalPool *pool, HalDevice *d)
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 @@ should_drive_be_ignored (HalPool *pool, HalDevice *d)
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 @@ update_volumes (GHalVolumeMonitor *monitor)
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 @@ update_discs (GHalVolumeMonitor *monitor)
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 @@ update_discs (GHalVolumeMonitor *monitor)
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 @@ update_discs (GHalVolumeMonitor *monitor)
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 @@ update_cameras (GHalVolumeMonitor *monitor)
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 @@ update_cameras (GHalVolumeMonitor *monitor)
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)
{
diff --git a/hal/ghalvolumemonitor.h b/hal/ghalvolumemonitor.h
index 4fac69bc..8a198b78 100644
--- a/hal/ghalvolumemonitor.h
+++ b/hal/ghalvolumemonitor.h
@@ -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 ())
diff --git a/hal/hal-device.c b/hal/hal-device.c
index 476ce0a6..2650aaf7 100644
--- a/hal/hal-device.c
+++ b/hal/hal-device.c
@@ -30,6 +30,7 @@ struct _HalDevicePrivate
LibHalContext *hal_ctx;
LibHalPropertySet *properties;
char *udi;
+ GTimeVal time_added;
};
enum {
@@ -91,6 +92,7 @@ static void
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 (GIOModule *module)
{
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;
+}
diff --git a/hal/hal-device.h b/hal/hal-device.h
index 6a5dd705..d2168f4c 100644
--- a/hal/hal-device.h
+++ b/hal/hal-device.h
@@ -91,4 +91,6 @@ gboolean hal_device_has_capability (HalDevice *d
gboolean hal_device_has_interface (HalDevice *device,
const char *interface);
+gboolean hal_device_is_recently_plugged_in (HalDevice *device);
+
#endif /* HAL_DEVICE_H */
diff --git a/hal/hal-pool.c b/hal/hal-pool.c
index 5af0900a..0f890e97 100644
--- a/hal/hal-pool.c
+++ b/hal/hal-pool.c
@@ -137,12 +137,18 @@ hal_pool_init (HalPool *pool)
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;
diff --git a/programs/Makefile.am b/programs/Makefile.am
index b104c355..766bfa2b 100644
--- a/programs/Makefile.am
+++ b/programs/Makefile.am
@@ -16,6 +16,7 @@ bin_PROGRAMS = \
gvfs-open \
gvfs-save \
gvfs-ls \
+ gvfs-tree \
gvfs-info \
gvfs-trash \
gvfs-rm \
@@ -30,6 +31,9 @@ bin_SCRIPTS = \
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_rm_LDADD = $(libraries)
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)
diff --git a/programs/gvfs-bash-completion.sh b/programs/gvfs-bash-completion.sh
new file mode 100755
index 00000000..1854663f
--- /dev/null
+++ b/programs/gvfs-bash-completion.sh
@@ -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
diff --git a/programs/gvfs-ls.c b/programs/gvfs-ls.c
index be202a7b..fb726b5d 100644
--- a/programs/gvfs-ls.c
+++ b/programs/gvfs-ls.c
@@ -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 @@ show_info (GFileInfo *info)
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 @@ show_info (GFileInfo *info)
{
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 @@ list (GFile *file)
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 @@ list (GFile *file)
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 @@ main (int argc, char *argv[])
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)
{
diff --git a/programs/gvfs-tree.c b/programs/gvfs-tree.c
new file mode 100644
index 00000000..c55b6621
--- /dev/null
+++ b/programs/gvfs-tree.c
@@ -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;
+}