summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Zeuthen <davidz@redhat.com>2008-03-03 19:36:51 +0000
committerDavid Zeuthen <davidz@src.gnome.org>2008-03-03 19:36:51 +0000
commit958b2491f3f400517dfa0fdacf23639d1ca67937 (patch)
tree7e642f1634e61e913b601335d9a53b33ef128603
parentf3c25f5b0370013f99d2dba907629ff37c29a7b6 (diff)
downloadgvfs-958b2491f3f400517dfa0fdacf23639d1ca67937.tar.gz
Add write support to gphoto2 backend. Also performance enhancements for
2008-03-03 David Zeuthen <davidz@redhat.com> Add write support to gphoto2 backend. Also performance enhancements for querying, enumerating and reading. Fixes bug #519651 * daemon/gvfsbackendgphoto2.c: (monitor_proxy_free), (DEBUG), (write_handle_free), (ensure_not_dirty), (dup_for_gphoto2), (monitors_emit_internal), (monitors_emit_created), (monitors_emit_deleted), (monitors_emit_changed), (caches_invalidate_all), (caches_invalidate_free_space), (caches_invalidate_dir), (caches_invalidate_file), (get_error_from_gphoto2), (release_device), (g_vfs_backend_gphoto2_finalize), (_gphoto2_logger_func), (g_vfs_backend_gphoto2_init), (find_udi_for_device), (_hal_device_removed), (split_filename_with_ignore_prefix), (add_ignore_prefix), (file_get_info), (is_directory), (is_regular), (is_directory_empty), (ensure_ignore_prefix), (do_mount), (try_mount), (do_unmount), (free_read_handle), (do_open_for_read), (try_read), (try_seek_on_read), (do_close_read), (do_query_info), (try_query_info), (do_enumerate), (try_enumerate), (do_query_fs_info), (try_query_fs_info), (do_make_directory), (do_slow_file_rename_in_same_dir), (do_file_rename_in_same_dir), (do_dir_rename_in_same_dir), (do_set_display_name), (do_delete), (do_create_internal), (do_create), (do_replace), (do_append_to), (do_write), (do_seek_on_write), (commit_write_handle), (do_close_write), (do_move), (vfs_dir_monitor_destroyed), (do_create_dir_monitor), (vfs_file_monitor_destroyed), (do_create_file_monitor), (g_vfs_backend_gphoto2_class_init): svn path=/trunk/; revision=1514
-rw-r--r--ChangeLog30
-rw-r--r--daemon/gvfsbackendgphoto2.c2774
2 files changed, 2397 insertions, 407 deletions
diff --git a/ChangeLog b/ChangeLog
index a6a9ad4f..836b63f6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,33 @@
+2008-03-03 David Zeuthen <davidz@redhat.com>
+
+ Add write support to gphoto2 backend. Also performance
+ enhancements for querying, enumerating and reading.
+ Fixes bug #519651
+
+ * daemon/gvfsbackendgphoto2.c: (monitor_proxy_free), (DEBUG),
+ (write_handle_free), (ensure_not_dirty), (dup_for_gphoto2),
+ (monitors_emit_internal), (monitors_emit_created),
+ (monitors_emit_deleted), (monitors_emit_changed),
+ (caches_invalidate_all), (caches_invalidate_free_space),
+ (caches_invalidate_dir), (caches_invalidate_file),
+ (get_error_from_gphoto2), (release_device),
+ (g_vfs_backend_gphoto2_finalize), (_gphoto2_logger_func),
+ (g_vfs_backend_gphoto2_init), (find_udi_for_device),
+ (_hal_device_removed), (split_filename_with_ignore_prefix),
+ (add_ignore_prefix), (file_get_info), (is_directory), (is_regular),
+ (is_directory_empty), (ensure_ignore_prefix), (do_mount),
+ (try_mount), (do_unmount), (free_read_handle), (do_open_for_read),
+ (try_read), (try_seek_on_read), (do_close_read), (do_query_info),
+ (try_query_info), (do_enumerate), (try_enumerate),
+ (do_query_fs_info), (try_query_fs_info), (do_make_directory),
+ (do_slow_file_rename_in_same_dir), (do_file_rename_in_same_dir),
+ (do_dir_rename_in_same_dir), (do_set_display_name), (do_delete),
+ (do_create_internal), (do_create), (do_replace), (do_append_to),
+ (do_write), (do_seek_on_write), (commit_write_handle),
+ (do_close_write), (do_move), (vfs_dir_monitor_destroyed),
+ (do_create_dir_monitor), (vfs_file_monitor_destroyed),
+ (do_create_file_monitor), (g_vfs_backend_gphoto2_class_init):
+
2008-03-03 Carlos Garcia Campos <carlosgc@gnome.org>
* daemon/mount.c: (read_mountable_config):
diff --git a/daemon/gvfsbackendgphoto2.c b/daemon/gvfsbackendgphoto2.c
index ae5cb4fa..44b0ca98 100644
--- a/daemon/gvfsbackendgphoto2.c
+++ b/daemon/gvfsbackendgphoto2.c
@@ -2,7 +2,7 @@
/* GVFS gphoto2 file system driver
*
- * Copyright (C) 2007 Red Hat, Inc.
+ * Copyright (C) 2007-2008 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
@@ -42,6 +42,7 @@
#include <gphoto2.h>
#include <libhal.h>
#include <dbus/dbus.h>
+#include <sys/time.h>
#include "gvfsbackendgphoto2.h"
#include "gvfsjobopenforread.h"
@@ -49,6 +50,28 @@
#include "gvfsjobseekread.h"
#include "gvfsjobqueryinfo.h"
#include "gvfsjobenumerate.h"
+#include "gvfsjobsetdisplayname.h"
+#include "gvfsjobopenforwrite.h"
+#include "gvfsjobwrite.h"
+#include "gvfsjobclosewrite.h"
+#include "gvfsjobcreatemonitor.h"
+#include "gvfsmonitor.h"
+#include "gvfsjobseekwrite.h"
+
+/* showing debug traces */
+#if 0
+#define DEBUG_SHOW_TRACES 1
+#endif
+
+/* showing libgphoto2 output */
+#if 0
+#define DEBUG_SHOW_LIBGPHOTO2_OUTPUT 1
+#endif
+
+/* use this to disable caching */
+#if 0
+#define DEBUG_NO_CACHING 1
+#endif
/* see this bug http://bugzilla.gnome.org/show_bug.cgi?id=518284 */
#define _I18N_LATER(x) x
@@ -58,20 +81,99 @@
/* 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
+ * - it's in; we support writing. yay.
+ * - though there's no way to rename an non-empty folder yet
+ * - there's an assumption, for caching, that the device won't
+ * be able to put files while we're using it. May have to
+ * revisit that if such devices exist.
+ * - one solution: make cache items valid for only five seconds or something
+ *
+ * - Note that most PTP devices (e.g. digital cameras) don't support writing
+ * - Most MTP devices (e.g. digital audio players) do
+ *
+ * - However, some MTP devices are just busted when using ~ backup
+ * style; see below. This is with my (davidz) Sandisk Sansa
+ * e250. This is probably a firmware bug; when investigating
+ * libgphoto2 reports everything as peachy.
+ *
+ * $ pwd
+ * /home/davidz/.gvfs/gphoto2 mount on usb%3A001,051/foo
+ * $ echo a > a
+ * $ echo b > b
+ * $ ls -l
+ * total 0
+ * -rw------- 1 davidz davidz 2 2008-03-02 13:22 a
+ * -rw------- 1 davidz davidz 2 2008-03-02 13:22 b
+ * $ cat a
+ * a
+ * $ cat b
+ * b
+ * $ mv b a
+ * $ ls -l
+ * total 0
+ * -rw------- 1 davidz davidz 2 2008-03-02 13:22 a
+ * $ cat a
+ * b
+ * $ rm a
+ *
+ * See, this worked fine.. Now see what happens if we
+ * use different files names
+ *
+ * $ echo a > file.txt
+ * $ echo b > file.txt~
+ * $ ls -l
+ * total 0
+ * -rw------- 1 davidz davidz 2 2008-03-02 13:22 file.txt
+ * -rw------- 1 davidz davidz 2 2008-03-02 13:22 file.txt~
+ * $ cat file.txt
+ * a
+ * $ cat file.txt~
+ * b
+ * $ mv file.txt~ file.txt
+ * $ ls -l
+ * total 0
+ * -rw------- 1 davidz davidz 0 1969-12-31 18:59 file.txt
+ * $ cat file.txt
+ * $
+ *
+ * Awesome. I hate hardware.
+ *
+ * - This is a bit bad as it affects most text editors (vim, emacs,
+ * gedit) and it actually results in data loss. However, there's
+ * little we can do about it.
+ *
+ * - Would be nice to test this on other MTP devices to verify
+ * it's indeed a firmware bug in the Sansa Sandisk e250.
+ *
+ * - This shouldn't affect stuff like Banshee or Rhythmbox using
+ * this backend for MTP support though; despite this bug basic
+ * file operations works nicely.
+ * - http://bugzilla.gnome.org/show_bug.cgi?id=520121
+ *
+ * - Need to test this with a native gio version of gedit that should
+ * use replace() directly instead of fooling around with ~-style
+ * backup files
*
* - 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()
+ * - http://bugzilla.gnome.org/show_bug.cgi?id=520123
+ *
+ * - adding a payload cache don't make much sense as libgphoto2 has a LRU cache already
+ * - (see comment in the do_close_write() function)
*
- * - 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
+ * - Support PTP/IP devices nicely
+ * - Need hardware for testing
+ * - Should actually work out of the box; just try mounting e.g.
+ * gphoto2://[ptpip:<something]/ from either Nautilus or via
+ * gvfs-mount(1).
+ * - Need to automatically unmount when the device stops answering
+ * - May need authentication bits
+ * - Need integration into network://
+ * - does such devices use DNS-SD or UPNP?
+ *
*/
struct _GVfsBackendGphoto2
@@ -86,7 +188,8 @@ struct _GVfsBackendGphoto2
/* see comment in ensure_ignore_prefix() */
char *ignore_prefix;
- int num_open_files;
+ /* list of open files */
+ int num_open_files_for_reading;
DBusConnection *dbus_connection;
LibHalContext *hal_ctx;
@@ -94,8 +197,29 @@ struct _GVfsBackendGphoto2
char *hal_name;
char *hal_icon_name;
+ /* whether we can write to the device */
+ gboolean can_write;
+
+ /* This lock protects all members in this class that are not
+ * used both on the main thread and on the IO thread.
+ *
+ * It is used, among other places, in the try_* functions to return
+ * already cached data quickly (to e.g. enumerate and get file info
+ * while we're reading or writing a file from the device).
+ *
+ * Must only be held for very short amounts of time (e.g. no IO).
+ */
+ GMutex *lock;
+
/* CACHES */
+ /* free_space is set to -1 if we don't know or have modified the
+ * device since last time we read it. If -1 we can't do
+ * try_query_fs_info() and will fall back to do_query_fs_info().
+ */
+ gint64 free_space;
+ gint64 capacity;
+
/* fully qualified path -> GFileInfo */
GHashTable *info_cache;
@@ -104,25 +228,317 @@ struct _GVfsBackendGphoto2
/* dir name -> CameraList of file names in given directory */
GHashTable *file_name_cache;
+
+ /* monitors (only used on the IO thread) */
+ GList *dir_monitor_proxies;
+ GList *file_monitor_proxies;
+
+ /* list of open write handles (only used on the IO thread) */
+ GList *open_write_handles;
};
G_DEFINE_TYPE (GVfsBackendGphoto2, g_vfs_backend_gphoto2, G_VFS_TYPE_BACKEND);
+/* ------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+ /* this is the path of the dir/file including ignore_prefix */
+ char *path;
+ GVfsMonitor *vfs_monitor;
+} MonitorProxy;
+
+static void
+monitor_proxy_free (MonitorProxy *proxy)
+{
+ g_free (proxy->path);
+ /* vfs_monitor is owned by the gvfs core; see the functions
+ * vfs_dir_monitor_destroyed() and do_create_monitor()
+ */
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+typedef struct {
+ /* filename as given from the vfs without ignore prefix e.g. /foo.txt */
+ char *filename;
+
+ /* filename with ignore prefix splitted into dir and name; e.g. "/store_00010001/" and "foo.txt" */
+ char *dir;
+ char *name;
+
+ char *data;
+ unsigned long int size;
+ unsigned long int cursor;
+ unsigned long int allocated_size;
+
+ gboolean job_is_replace;
+ gboolean job_is_append_to;
+
+ gboolean delete_before;
+
+ gboolean is_dirty;
+} WriteHandle;
+
+/* how much more memory to ask for when using g_realloc() when writing a file */
+#define WRITE_INCREMENT 4096
+
+typedef struct {
+ CameraFile *file;
+
+ const char *data;
+ unsigned long int size;
+ unsigned long int cursor;
+} ReadHandle;
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+DEBUG (const gchar *message, ...)
+{
+#ifdef DEBUG_SHOW_TRACES
+ va_list args;
+ va_start (args, message);
+ g_vfprintf (stderr, message, args);
+ va_end (args);
+ g_fprintf (stderr, "\n");
+ fflush (stderr);
+#endif
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static int commit_write_handle (GVfsBackendGphoto2 *gphoto2_backend, WriteHandle *write_handle);
+
+static void
+write_handle_free (WriteHandle *write_handle)
+{
+ g_free (write_handle->filename);
+ g_free (write_handle->dir);
+ g_free (write_handle->name);
+ g_free (write_handle->data);
+ g_free (write_handle);
+}
+
+/* This must be called before reading from the device to ensure that
+ * all pending writes are written to the device.
+ *
+ * Must only be called on the IO thread.
+ */
+static void
+ensure_not_dirty (GVfsBackendGphoto2 *gphoto2_backend)
+{
+ GList *l;
+
+ for (l = gphoto2_backend->open_write_handles; l != NULL; l = l->next)
+ {
+ WriteHandle *write_handle = l->data;
+
+ DEBUG ("ensure_not_dirty: looking at handle for '%s", write_handle->filename);
+
+ if (write_handle->is_dirty)
+ commit_write_handle (gphoto2_backend, write_handle);
+ }
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+/* used when gphoto2 will take ownership of this data for it's LRU cache - and will use free(3) to free it */
+static char *
+dup_for_gphoto2 (char *gmem, unsigned long int size)
+{
+ char *mem;
+ mem = malloc (size);
+ memcpy (mem, gmem, size);
+ return mem;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+monitors_emit_internal (GVfsBackendGphoto2 *gphoto2_backend,
+ const char *dir,
+ const char *name,
+ GFileMonitorEvent event,
+ const char *event_name)
+{
+ GList *l;
+ char *filepath;
+
+ g_return_if_fail (g_str_has_prefix (dir, gphoto2_backend->ignore_prefix));
+
+ DEBUG ("monitors_emit_internal() %s for '%s' '%s'", event_name, dir, name);
+
+ for (l = gphoto2_backend->dir_monitor_proxies; l != NULL; l = l->next)
+ {
+ MonitorProxy *proxy = l->data;
+ if (strcmp (proxy->path, dir) == 0)
+ {
+ char *path;
+ path = g_build_filename (dir + strlen (gphoto2_backend->ignore_prefix), name, NULL);
+ g_vfs_monitor_emit_event (proxy->vfs_monitor, event, path, NULL);
+ DEBUG (" emitted %s for '%s' on dir monitor for '%s'", event_name, path, dir);
+ g_free (path);
+ }
+ }
+
+ filepath = g_build_filename (dir, name, NULL);
+ for (l = gphoto2_backend->file_monitor_proxies; l != NULL; l = l->next)
+ {
+ MonitorProxy *proxy = l->data;
+ if (strcmp (proxy->path, filepath) == 0)
+ {
+ const char *path = filepath + strlen (gphoto2_backend->ignore_prefix);
+ g_vfs_monitor_emit_event (proxy->vfs_monitor, event, path, NULL);
+ DEBUG (" emitted %s for '%s' on file monitor", event_name, path);
+ }
+ }
+ g_free (filepath);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+/* call this when a file/directory have been added to a directory */
+static void
+monitors_emit_created (GVfsBackendGphoto2 *gphoto2_backend, const char *dir, const char *name)
+{
+ DEBUG ("monitors_emit_created(): '%s' '%s'", dir, name);
+ monitors_emit_internal (gphoto2_backend, dir, name, G_FILE_MONITOR_EVENT_CREATED, "CREATED");
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+/* call this when a file/directory have been deleted from a directory */
+static void
+monitors_emit_deleted (GVfsBackendGphoto2 *gphoto2_backend, const char *dir, const char *name)
+{
+ DEBUG ("monitors_emit_deleted(): '%s' '%s'", dir, name);
+ monitors_emit_internal (gphoto2_backend, dir, name, G_FILE_MONITOR_EVENT_DELETED, "DELETED");
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+/* call this when a file/directory have been changed in a directory */
+static void
+monitors_emit_changed (GVfsBackendGphoto2 *gphoto2_backend, const char *dir, const char *name)
+{
+ DEBUG ("monitors_emit_changed(): '%s' '%s'", dir, name);
+ monitors_emit_internal (gphoto2_backend, dir, name, G_FILE_MONITOR_EVENT_CHANGED, "CHANGED");
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+caches_invalidate_all (GVfsBackendGphoto2 *gphoto2_backend)
+{
+ DEBUG ("caches_invalidate_all()");
+
+ g_mutex_lock (gphoto2_backend->lock);
+ if (gphoto2_backend->dir_name_cache != NULL)
+ g_hash_table_remove_all (gphoto2_backend->dir_name_cache);
+ if (gphoto2_backend->file_name_cache != NULL)
+ g_hash_table_remove_all (gphoto2_backend->file_name_cache);
+ if (gphoto2_backend->info_cache != NULL)
+ g_hash_table_remove_all (gphoto2_backend->info_cache);
+ gphoto2_backend->capacity = -1;
+ gphoto2_backend->free_space = -1;
+ g_mutex_unlock (gphoto2_backend->lock);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+caches_invalidate_free_space (GVfsBackendGphoto2 *gphoto2_backend)
+{
+ g_mutex_lock (gphoto2_backend->lock);
+ gphoto2_backend->free_space = -1;
+ g_mutex_unlock (gphoto2_backend->lock);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+caches_invalidate_dir (GVfsBackendGphoto2 *gphoto2_backend, const char *dir)
+{
+ DEBUG ("caches_invalidate_dir() for '%s'", dir);
+ g_mutex_lock (gphoto2_backend->lock);
+ g_hash_table_remove (gphoto2_backend->dir_name_cache, dir);
+ g_hash_table_remove (gphoto2_backend->file_name_cache, dir);
+ g_hash_table_remove (gphoto2_backend->info_cache, dir);
+ g_mutex_unlock (gphoto2_backend->lock);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+caches_invalidate_file (GVfsBackendGphoto2 *gphoto2_backend, const char *dir, const char *name)
+{
+ char *full_name;
+
+ full_name = g_build_filename (dir, name, NULL);
+
+ g_mutex_lock (gphoto2_backend->lock);
+ /* this is essentially: caches_invalidate_dir (gphoto2_backend, dir); */
+ g_hash_table_remove (gphoto2_backend->dir_name_cache, dir);
+ g_hash_table_remove (gphoto2_backend->file_name_cache, dir);
+ g_hash_table_remove (gphoto2_backend->info_cache, dir);
+
+ g_hash_table_remove (gphoto2_backend->info_cache, full_name);
+ g_mutex_unlock (gphoto2_backend->lock);
+
+ DEBUG ("caches_invalidate_file() for '%s'", full_name);
+ g_free (full_name);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
static GError *
-get_error_from_gphoto2 (const char *message, int gphoto2_error_code)
+get_error_from_gphoto2 (const char *message, int rc)
{
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));
+
+ switch (rc)
+ {
+ case GP_ERROR_FILE_EXISTS:
+ case GP_ERROR_DIRECTORY_EXISTS:
+ /* Translator: %s represents a more specific error message and %d the specific error code */
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_EXISTS, _I18N_LATER("%s: %d: Directory or file exists"), message, rc);
+ break;
+
+ case GP_ERROR_FILE_NOT_FOUND:
+ case GP_ERROR_DIRECTORY_NOT_FOUND:
+ /* Translator: %s represents a more specific error message and %d the specific error code */
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND, _I18N_LATER("%s: %d: No such file or directory"), message, rc);
+ break;
+
+ case GP_ERROR_PATH_NOT_ABSOLUTE:
+ /* Translator: %s represents a more specific error message and %d the specific error code */
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_INVALID_FILENAME, _I18N_LATER("%s: %d: Invalid filename"), message, rc);
+ break;
+
+ case GP_ERROR_NOT_SUPPORTED:
+ /* Translator: %s represents a more specific error message and %d the specific error code */
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED, _I18N_LATER("%s: %d: Not Supported"), message, rc);
+ break;
+
+ default:
+ error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_FAILED, "%s: %d: %s", message, rc, gp_result_as_string (rc));
+ break;
+ }
return error;
}
+/* ------------------------------------------------------------------------------------------------- */
+
static void
release_device (GVfsBackendGphoto2 *gphoto2_backend)
{
+ GList *l;
+
g_free (gphoto2_backend->gphoto2_port);
gphoto2_backend->gphoto2_port = NULL;
@@ -176,14 +592,40 @@ release_device (GVfsBackendGphoto2 *gphoto2_backend)
g_hash_table_unref (gphoto2_backend->file_name_cache);
gphoto2_backend->file_name_cache = NULL;
}
+
+ for (l = gphoto2_backend->dir_monitor_proxies; l != NULL; l = l->next)
+ {
+ MonitorProxy *proxy = l->data;
+ monitor_proxy_free (proxy);
+ }
+ g_list_free (gphoto2_backend->dir_monitor_proxies);
+ gphoto2_backend->dir_monitor_proxies = NULL;
+
+ for (l = gphoto2_backend->file_monitor_proxies; l != NULL; l = l->next)
+ {
+ MonitorProxy *proxy = l->data;
+ monitor_proxy_free (proxy);
+ }
+ g_list_free (gphoto2_backend->file_monitor_proxies);
+ gphoto2_backend->file_monitor_proxies = NULL;
+
+ if (gphoto2_backend->lock != NULL)
+ {
+ g_mutex_free (gphoto2_backend->lock);
+ gphoto2_backend->lock = NULL;
+ }
+ gphoto2_backend->capacity = -1;
+ gphoto2_backend->free_space = -1;
}
+/* ------------------------------------------------------------------------------------------------- */
+
static void
g_vfs_backend_gphoto2_finalize (GObject *object)
{
GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (object);
- /*g_warning ("finalizing %p", object);*/
+ DEBUG ("finalizing %p", object);
release_device (gphoto2_backend);
@@ -191,21 +633,40 @@ g_vfs_backend_gphoto2_finalize (GObject *object)
(*G_OBJECT_CLASS (g_vfs_backend_gphoto2_parent_class)->finalize) (object);
}
+/* ------------------------------------------------------------------------------------------------- */
+
+#ifdef DEBUG_SHOW_LIBGPHOTO2_OUTPUT
+static void
+_gphoto2_logger_func (GPLogLevel level, const char *domain, const char *format, va_list args, void *data)
+{
+ g_fprintf (stderr, "libgphoto2: %s: ", domain);
+ g_vfprintf (stderr, message, args);
+ va_end (args);
+ g_fprintf (stderr, "\n");
+}
+#endif
+
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);*/
+ DEBUG ("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);
+
+#ifdef DEBUG_SHOW_LIBGPHOTO2_OUTPUT
+ gp_log_add_func (GP_LOG_ALL, _gphoto2_logger_func, NULL);
+#endif
}
+/* ------------------------------------------------------------------------------------------------- */
+
static char *
compute_icon_name (GVfsBackendGphoto2 *gphoto2_backend)
{
@@ -223,6 +684,8 @@ compute_icon_name (GVfsBackendGphoto2 *gphoto2_backend)
return result;
}
+/* ------------------------------------------------------------------------------------------------- */
+
static char *
compute_display_name (GVfsBackendGphoto2 *gphoto2_backend)
{
@@ -241,6 +704,7 @@ compute_display_name (GVfsBackendGphoto2 *gphoto2_backend)
return result;
}
+/* ------------------------------------------------------------------------------------------------- */
static void
find_udi_for_device (GVfsBackendGphoto2 *gphoto2_backend)
@@ -289,7 +753,7 @@ find_udi_for_device (GVfsBackendGphoto2 *gphoto2_backend)
g_strfreev (tokens);
- /*g_warning ("Parsed '%s' into bus=%d device=%d", gphoto2_backend->gphoto2_port, usb_bus_num, usb_device_num);*/
+ DEBUG ("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",
@@ -329,8 +793,8 @@ find_udi_for_device (GVfsBackendGphoto2 *gphoto2_backend)
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);*/
+ DEBUG ("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)
@@ -339,7 +803,7 @@ find_udi_for_device (GVfsBackendGphoto2 *gphoto2_backend)
const char *parent_udi;
LibHalPropertySet *ps2;
- /*g_warning ("udi '%s' is the one!", gphoto2_backend->hal_udi);*/
+ DEBUG ("udi '%s' is the one!", udi);
/* IMPORTANT:
*
@@ -422,6 +886,8 @@ find_udi_for_device (GVfsBackendGphoto2 *gphoto2_backend)
}
}
+/* ------------------------------------------------------------------------------------------------- */
+
static void
_hal_device_removed (LibHalContext *hal_ctx, const char *udi)
{
@@ -431,13 +897,465 @@ _hal_device_removed (LibHalContext *hal_ctx, const char *udi)
if (gphoto2_backend->hal_udi != NULL && strcmp (udi, gphoto2_backend->hal_udi) == 0)
{
- /*g_warning ("we have been removed!");*/
+ DEBUG ("we have been removed!");
+
+ /* nuke all caches so we're a bit more valgrind friendly */
+ caches_invalidate_all (gphoto2_backend);
/* TODO: need a cleaner way to force unmount ourselves */
exit (1);
}
}
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+split_filename_with_ignore_prefix (GVfsBackendGphoto2 *gphoto2_backend, const char *filename, char **dir, char **name)
+{
+ char *s;
+
+ s = g_path_get_dirname (filename);
+ if (s[0] == '/')
+ *dir = g_strconcat (gphoto2_backend->ignore_prefix, s + 1, NULL);
+ else
+ *dir = g_strconcat (gphoto2_backend->ignore_prefix, s, NULL);
+ g_free (s);
+
+ if (strcmp (filename, "/") == 0)
+ *name = g_strdup ("");
+ else
+ *name = g_path_get_basename (filename);
+
+ s = *dir;
+ if (s[strlen(s)] == '/')
+ s[strlen(s)] = '\0';
+
+ /*DEBUG ("split_filename_with_ignore_prefix: '%s' -> '%s' '%s'", filename, *dir, *name);*/
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static char *
+add_ignore_prefix (GVfsBackendGphoto2 *gphoto2_backend, const char *filename)
+{
+ char *result;
+
+ if (filename[0] == '/')
+ result = g_strconcat (gphoto2_backend->ignore_prefix, filename + 1, NULL);
+ else
+ result = g_strconcat (gphoto2_backend->ignore_prefix, filename, NULL);
+
+ /*DEBUG ("add_ignore_prefix: '%s' -> '%s'", filename, result);*/
+ return result;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+/* the passed 'dir' variable must contain ignore_prefix */
+static gboolean
+file_get_info (GVfsBackendGphoto2 *gphoto2_backend,
+ const char *dir,
+ const char *name,
+ GFileInfo *info,
+ GError **error,
+ gboolean try_cache_only)
+{
+ int rc;
+ gboolean ret;
+ CameraFileInfo gp_info;
+ char *full_path;
+ GFileInfo *cached_info;
+ GTimeVal mtime;
+ char *mime_type;
+ GIcon *icon;
+ unsigned int n;
+
+ ret = FALSE;
+
+ full_path = g_build_filename (dir, name, NULL);
+ DEBUG ("file_get_info() try_cache_only=%d dir='%s', name='%s'\n"
+ " full_path='%s'",
+ try_cache_only, dir, name, full_path, gphoto2_backend->ignore_prefix);
+
+
+ /* first look up cache */
+ g_mutex_lock (gphoto2_backend->lock);
+ cached_info = g_hash_table_lookup (gphoto2_backend->info_cache, full_path);
+ if (cached_info != NULL)
+ {
+ g_file_info_copy_into (cached_info, info);
+ g_mutex_unlock (gphoto2_backend->lock);
+ DEBUG (" Using cached info %p for '%s'", cached_info, full_path);
+ ret = TRUE;
+ goto out;
+ }
+ g_mutex_unlock (gphoto2_backend->lock);
+
+ if (try_cache_only)
+ goto out;
+
+ ensure_not_dirty (gphoto2_backend);
+
+ DEBUG (" No cached info for '%s'", full_path);
+
+ /* Since we're caching stuff, make sure all information we store is set */
+ g_file_info_unset_attribute_mask (info);
+
+ /* handle root directory */
+ if (strcmp (full_path, gphoto2_backend->ignore_prefix) == 0 || strcmp (full_path, "/") == 0)
+ {
+ char *display_name;
+ display_name = compute_display_name (gphoto2_backend);
+ g_file_info_set_display_name (info, display_name);
+ g_file_info_set_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_object_unref (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, gphoto2_backend->can_write);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, gphoto2_backend->can_write);
+ 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);
+ ret = TRUE;
+ DEBUG (" Generating info (root folder) for '%s'", full_path);
+ goto add_to_cache;
+ }
+
+ rc = gp_camera_file_get_info (gphoto2_backend->camera,
+ dir,
+ name,
+ &gp_info,
+ gphoto2_backend->context);
+ if (rc != 0)
+ {
+ CameraList *list;
+ gboolean is_folder;
+
+ /* gphoto2 doesn't know about this file.. it may be a folder; try that */
+ is_folder = FALSE;
+ gp_list_new (&list);
+ rc = gp_camera_folder_list_folders (gphoto2_backend->camera,
+ dir,
+ list,
+ gphoto2_backend->context);
+ if (rc == 0)
+ {
+ for (n = 0; n < gp_list_count (list) && !is_folder; n++)
+ {
+ const char *folder_name;
+ gp_list_get_name (list, n, &folder_name);
+ if (strcmp (folder_name, name) == 0)
+ {
+ is_folder = TRUE;
+ }
+ }
+ }
+ gp_list_free (list);
+
+ if (is_folder)
+ {
+ 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, gphoto2_backend->can_write);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, gphoto2_backend->can_write);
+ 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, gphoto2_backend->can_write);
+ g_file_info_set_is_hidden (info, name != NULL && name[0] == '.');
+ ret = TRUE;
+ DEBUG (" Generating info (folder) for '%s'", full_path);
+ goto add_to_cache;
+ }
+
+ /* nope, not a folder either.. error out.. */
+ if (error != NULL)
+ {
+ *error = g_error_new (G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ _I18N_LATER("No such file or directory"));
+ }
+ 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);
+ DEBUG (" 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, gphoto2_backend->can_write);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, gphoto2_backend->can_write);
+ 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, gphoto2_backend->can_write);
+ g_file_info_set_is_hidden (info, name != NULL && name[0] == '.');
+
+ if (gp_info.file.fields & GP_FILE_INFO_PERMISSIONS) {
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ gp_info.file.permissions & GP_FILE_PERM_DELETE);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE,
+ gp_info.file.permissions & GP_FILE_PERM_DELETE);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME,
+ gp_info.file.permissions & GP_FILE_PERM_DELETE);
+ }
+
+ ret = TRUE;
+ DEBUG (" Generating info (file) for '%s'", full_path);
+
+ add_to_cache:
+ /* add this sucker to the cache */
+ if (ret == TRUE)
+ {
+#ifndef DEBUG_NO_CACHING
+ cached_info = g_file_info_dup (info);
+ DEBUG (" Storing cached info %p for '%s'", cached_info, full_path);
+ g_mutex_lock (gphoto2_backend->lock);
+ g_hash_table_insert (gphoto2_backend->info_cache, g_strdup (full_path), cached_info);
+ g_mutex_unlock (gphoto2_backend->lock);
+#endif
+ }
+
+ out:
+ g_free (full_path);
+ return ret;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_directory (GVfsBackendGphoto2 *gphoto2_backend, const char *dir, const char *name)
+{
+ GFileInfo *info;
+ gboolean ret;
+
+ ret = FALSE;
+
+ info = g_file_info_new ();
+ if (!file_get_info (gphoto2_backend, dir, name, info, NULL, FALSE))
+ goto out;
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ ret = TRUE;
+
+ out:
+ g_object_unref (info);
+ return ret;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_regular (GVfsBackendGphoto2 *gphoto2_backend, const char *dir, const char *name)
+{
+ GFileInfo *info;
+ gboolean ret;
+
+ ret = FALSE;
+
+ info = g_file_info_new ();
+ if (!file_get_info (gphoto2_backend, dir, name, info, NULL, FALSE))
+ goto out;
+
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR)
+ ret = TRUE;
+
+ out:
+ g_object_unref (info);
+ return ret;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static gboolean
+is_directory_empty (GVfsBackendGphoto2 *gphoto2_backend, const char *dir)
+{
+ CameraList *list;
+ gboolean ret;
+ int rc;
+ int num_dirs;
+ int num_files;
+
+ DEBUG ("is_directory_empty begin (%s)", dir);
+
+ /* TODO: use cache */
+
+ ret = FALSE;
+ num_dirs = 0;
+ num_files = 0;
+
+ gp_list_new (&list);
+ rc = gp_camera_folder_list_files (gphoto2_backend->camera,
+ dir,
+ list,
+ gphoto2_backend->context);
+ if (rc == 0)
+ num_files = gp_list_count (list);
+ gp_list_free (list);
+
+ if (num_files > 0)
+ goto out;
+
+ gp_list_new (&list);
+ rc = gp_camera_folder_list_folders (gphoto2_backend->camera,
+ dir,
+ list,
+ gphoto2_backend->context);
+ if (rc == 0)
+ num_dirs = gp_list_count (list);
+ gp_list_free (list);
+
+ if (num_dirs == 0 && num_files == 0)
+ ret = TRUE;
+
+ out:
+ DEBUG (" is_directory_empty (%s) -> %d", dir, ret);
+ return ret;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+/* 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.
+ *
+ * This function needs to be called from do_mount().
+ */
+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;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
static void
do_mount (GVfsBackend *backend,
GVfsJobMount *job,
@@ -457,8 +1375,10 @@ do_mount (GVfsBackend *backend,
GPPortInfoList *il = NULL;
int n;
DBusError dbus_error;
+ CameraStorageInformation *storage_info;
+ int num_storage_info;
- /*g_warning ("do_mount %p", gphoto2_backend);*/
+ DEBUG ("do_mount %p", gphoto2_backend);
/* setup libhal */
@@ -503,7 +1423,7 @@ do_mount (GVfsBackend *backend,
/* setup gphoto2 */
host = g_mount_spec_get (mount_spec, "host");
- /*g_warning ("host='%s'", host);*/
+ DEBUG (" 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"));
@@ -516,7 +1436,7 @@ do_mount (GVfsBackend *backend,
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);*/
+ DEBUG (" decoded host='%s'", gphoto2_backend->gphoto2_port);
find_udi_for_device (gphoto2_backend);
@@ -563,7 +1483,7 @@ do_mount (GVfsBackend *backend,
return;
}
- /*g_warning ("gphoto2_port='%s'", gphoto2_backend->gphoto2_port);*/
+ DEBUG (" 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)
@@ -585,7 +1505,7 @@ do_mount (GVfsBackend *backend,
return;
}
- /*g_warning ("'%s' '%s' '%s'", info.name, info.path, info.library_filename);*/
+ DEBUG (" '%s' '%s' '%s'", info.name, info.path, info.library_filename);
/* set port */
rc = gp_camera_set_port_info (gphoto2_backend->camera, info);
@@ -609,6 +1529,12 @@ do_mount (GVfsBackend *backend,
return;
}
+ if (!ensure_ignore_prefix (gphoto2_backend, G_VFS_JOB (job)))
+ {
+ 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);
@@ -620,8 +1546,26 @@ do_mount (GVfsBackend *backend,
g_free (icon_name);
g_free (fuse_name);
+ gphoto2_backend->can_write = FALSE;
+ rc = gp_camera_get_storageinfo (gphoto2_backend->camera, &storage_info, &num_storage_info, gphoto2_backend->context);
+ if (rc == 0)
+ {
+ if (num_storage_info >= 1)
+ {
+ if (storage_info[0].fields & GP_STORAGEINFO_ACCESS && storage_info[0].access == GP_STORAGEINFO_AC_READWRITE)
+ {
+ gphoto2_backend->can_write = TRUE;
+ }
+ }
+ }
+ DEBUG (" can_write = %d", gphoto2_backend->can_write);
+
g_vfs_job_succeeded (G_VFS_JOB (job));
+ gphoto2_backend->free_space = -1;
+
+ gphoto2_backend->lock = g_mutex_new ();
+
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);
@@ -642,9 +1586,11 @@ do_mount (GVfsBackend *backend,
g_free,
(GDestroyNotify) gp_list_unref);
- /*g_warning ("mounted %p", gphoto2_backend);*/
+ DEBUG (" mounted %p", gphoto2_backend);
}
+/* ------------------------------------------------------------------------------------------------- */
+
static gboolean
try_mount (GVfsBackend *backend,
GVfsJobMount *job,
@@ -656,13 +1602,13 @@ try_mount (GVfsBackend *backend,
GError *error = NULL;
GMountSpec *gphoto2_mount_spec;
- /*g_warning ("try_mount %p", backend);*/
+ DEBUG ("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);*/
+ DEBUG (" host=%s", host);
if (host == NULL)
{
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _I18N_LATER("No camera specified"));
@@ -678,6 +1624,7 @@ try_mount (GVfsBackend *backend,
return FALSE;
}
+/* ------------------------------------------------------------------------------------------------- */
static void
do_unmount (GVfsBackend *backend,
@@ -685,11 +1632,14 @@ do_unmount (GVfsBackend *backend,
{
GError *error;
GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ int num_open_files;
+
+ num_open_files = gphoto2_backend->num_open_files_for_reading + g_list_length (gphoto2_backend->open_write_handles);
- if (gphoto2_backend->num_open_files > 0)
+ if (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);
+ _I18N_LATER("File system is busy: %d open files"), num_open_files);
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
return;
}
@@ -697,103 +1647,18 @@ do_unmount (GVfsBackend *backend,
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;
+ DEBUG ("unmounted %p", backend);
}
-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);
+ {
+ gp_file_unref (read_handle->file);
+ }
g_free (read_handle);
}
@@ -806,19 +1671,30 @@ do_open_for_read (GVfsBackend *backend,
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;
+ DEBUG ("open_for_read (%s)", filename);
- s = g_path_get_dirname (filename);
- dir = g_strconcat (gphoto2_backend->ignore_prefix, s, NULL);
- g_free (s);
- name = g_path_get_basename (filename);
+ ensure_not_dirty (gphoto2_backend);
+
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
- /*g_warning ("open_for_read (%s)", filename);*/
+ if (is_directory (gphoto2_backend, dir, name))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_IS_DIRECTORY,
+ _("Can't open directory"));
+ goto out;
+ }
+
+ if (!is_regular (gphoto2_backend, dir, name))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ _("No such file"));
+ goto out;
+ }
read_handle = g_new0 (ReadHandle, 1);
rc = gp_file_new (&read_handle->file);
@@ -853,10 +1729,12 @@ do_open_for_read (GVfsBackend *backend,
goto out;
}
- /*g_warning ("data=%p size=%ld", read_handle->data, read_handle->size);*/
-
- gphoto2_backend->num_open_files++;
+ DEBUG (" data=%p size=%ld handle=%p", read_handle->data, read_handle->size, read_handle);
+ g_mutex_lock (gphoto2_backend->lock);
+ gphoto2_backend->num_open_files_for_reading++;
+ g_mutex_unlock (gphoto2_backend->lock);
+
read_handle->cursor = 0;
g_vfs_job_open_for_read_set_can_seek (job, TRUE);
@@ -868,26 +1746,28 @@ do_open_for_read (GVfsBackend *backend,
g_free (dir);
}
-static void
-do_read (GVfsBackend *backend,
- GVfsJobRead *job,
- GVfsBackendHandle handle,
- char *buffer,
- gsize bytes_requested)
+/* ------------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_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);*/
+ DEBUG ("do_read() %d @ %ld of %ld, handle=%p", bytes_requested, read_handle->cursor, read_handle->size, handle);
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;
@@ -901,20 +1781,23 @@ do_read (GVfsBackend *backend,
g_vfs_job_read_set_size (job, bytes_to_copy);
g_vfs_job_succeeded (G_VFS_JOB (job));
+ return TRUE;
}
-static void
-do_seek_on_read (GVfsBackend *backend,
- GVfsJobSeekRead *job,
- GVfsBackendHandle handle,
- goffset offset,
- GSeekType type)
+/* ------------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_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);*/
+ DEBUG ("seek_on_read() offset=%d, type=%d, handle=%p", (int)offset, type, handle);
switch (type)
{
@@ -930,202 +1813,44 @@ do_seek_on_read (GVfsBackend *backend,
break;
}
- if (new_offset < 0 || new_offset >= read_handle->size)
+ 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);
+ 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));
}
+ return TRUE;
}
+/* ------------------------------------------------------------------------------------------------- */
+
+/* cannot be async because we unref the CameraFile */
static void
do_close_read (GVfsBackend *backend,
- GVfsJobCloseRead *job,
- GVfsBackendHandle handle)
+ GVfsJobCloseRead *job,
+ GVfsBackendHandle handle)
{
ReadHandle *read_handle = (ReadHandle *) handle;
GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
- /*g_warning ("close ()");*/
+ DEBUG ("close_read() handle=%p", handle);
free_read_handle (read_handle);
- gphoto2_backend->num_open_files--;
+ g_mutex_lock (gphoto2_backend->lock);
+ gphoto2_backend->num_open_files_for_reading--;
+ g_mutex_unlock (gphoto2_backend->lock);
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,
@@ -1137,59 +1862,66 @@ do_query_info (GVfsBackend *backend,
{
GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
GError *error;
+ char *dir;
+ char *name;
- if (!ensure_ignore_prefix (gphoto2_backend, G_VFS_JOB (job)))
- return;
+ DEBUG ("query_info (%s)", filename);
- /*g_warning ("get_file_info (%s)", filename);*/
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
- if (strcmp (filename, "/") == 0)
+ if (!file_get_info (gphoto2_backend, dir, name, info, &error, FALSE))
{
- 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));
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
}
else
{
- char *s;
- char *dir;
- char *name;
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+ }
+
+ g_free (name);
+ g_free (dir);
+}
- 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));
- }
+static gboolean
+try_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;
+ char *dir;
+ char *name;
+ gboolean ret;
- g_free (name);
- g_free (dir);
+ DEBUG ("try_query_info (%s)", filename);
+
+ ret = FALSE;
+
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
+
+ if (!file_get_info (gphoto2_backend, dir, name, info, &error, TRUE))
+ {
+ DEBUG (" BUU no info from cache for try_query_info (%s)", filename);
+ goto out;
}
+ DEBUG (" YAY got info from cache for try_query_info (%s)", filename);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+ ret = TRUE;
+ out:
+ g_free (name);
+ g_free (dir);
+ return ret;
}
+/* ------------------------------------------------------------------------------------------------- */
+
static void
do_enumerate (GVfsBackend *backend,
GVfsJobEnumerate *job,
@@ -1207,21 +1939,49 @@ do_enumerate (GVfsBackend *backend,
char *filename;
gboolean using_cached_dir_list;
gboolean using_cached_file_list;
-
- if (!ensure_ignore_prefix (gphoto2_backend, G_VFS_JOB (job)))
- return;
+ char *as_dir;
+ char *as_name;
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);*/
+ filename = add_ignore_prefix (gphoto2_backend, given_filename);
+ DEBUG ("enumerate (%s)", given_filename);
+
+ split_filename_with_ignore_prefix (gphoto2_backend, given_filename, &as_dir, &as_name);
+ if (!is_directory (gphoto2_backend, as_dir, as_name))
+ {
+ if (is_regular (gphoto2_backend, as_dir, as_name))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_DIRECTORY,
+ _I18N_LATER("Not a directory"));
+ }
+ else
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ _I18N_LATER("No such file or directory"));
+ }
+ g_free (as_dir);
+ g_free (as_name);
+ return;
+ }
+ g_free (as_dir);
+ g_free (as_name);
/* first, list the folders */
+ g_mutex_lock (gphoto2_backend->lock);
list = g_hash_table_lookup (gphoto2_backend->dir_name_cache, filename);
if (list == NULL)
{
+ g_mutex_unlock (gphoto2_backend->lock);
+
+ ensure_not_dirty (gphoto2_backend);
+
+ DEBUG (" Generating dir list for dir '%s'", filename);
+
gp_list_new (&list);
rc = gp_camera_folder_list_folders (gphoto2_backend->camera,
filename,
@@ -1237,51 +1997,54 @@ do_enumerate (GVfsBackend *backend,
}
else
{
- /*g_warning ("Using cached dirlist for dir '%s'", filename);*/
- gp_list_ref (list);
+ DEBUG (" Using cached dir list for dir '%s'", filename);
using_cached_dir_list = TRUE;
+ gp_list_ref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
}
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);*/
-
+ DEBUG (" enum folder '%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);
-
+ if (!file_get_info (gphoto2_backend, filename, name, info, &error, FALSE))
+ {
+ 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_dir_list)
{
- gp_list_ref (list);
+#ifndef DEBUG_NO_CACHING
+ g_mutex_lock (gphoto2_backend->lock);
g_hash_table_insert (gphoto2_backend->dir_name_cache, g_strdup (filename), list);
+ g_mutex_unlock (gphoto2_backend->lock);
+#endif
+ }
+ else
+ {
+ g_mutex_lock (gphoto2_backend->lock);
+ gp_list_unref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
}
- gp_list_unref (list);
/* then list the files in each folder */
+ g_mutex_lock (gphoto2_backend->lock);
list = g_hash_table_lookup (gphoto2_backend->file_name_cache, filename);
if (list == NULL)
{
+ g_mutex_unlock (gphoto2_backend->lock);
+ ensure_not_dirty (gphoto2_backend);
+
+ DEBUG (" Generating file list for dir '%s'", filename);
+
gp_list_new (&list);
rc = gp_camera_folder_list_files (gphoto2_backend->camera,
filename,
@@ -1297,18 +2060,20 @@ do_enumerate (GVfsBackend *backend,
}
else
{
- /*g_warning ("Using cached file list for dir '%s'", filename);*/
- gp_list_ref (list);
+ DEBUG (" Using cached file list for dir '%s'", filename);
using_cached_file_list = TRUE;
+ gp_list_ref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
}
for (n = 0; n < gp_list_count (list); n++)
{
const char *name;
gp_list_get_name (list, n, &name);
+ DEBUG (" enum file '%s'", name);
info = g_file_info_new ();
- if (!_set_info (gphoto2_backend, filename, name, info, &error))
+ if (!file_get_info (gphoto2_backend, filename, name, info, &error, FALSE))
{
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
g_list_foreach (l, (GFunc) g_object_unref, NULL);
@@ -1320,10 +2085,109 @@ do_enumerate (GVfsBackend *backend,
}
if (!using_cached_file_list)
{
- gp_list_ref (list);
+#ifndef DEBUG_NO_CACHING
+ g_mutex_lock (gphoto2_backend->lock);
g_hash_table_insert (gphoto2_backend->file_name_cache, g_strdup (filename), list);
+ g_mutex_unlock (gphoto2_backend->lock);
+#endif
+ }
+ else
+ {
+ g_mutex_lock (gphoto2_backend->lock);
+ gp_list_unref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
+ }
+
+ /* 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 gboolean
+try_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;
+ char *filename;
+ const char *name;
+
+ l = NULL;
+
+ filename = add_ignore_prefix (gphoto2_backend, given_filename);
+ DEBUG ("try_enumerate (%s)", given_filename);
+
+ /* first, list the folders */
+ g_mutex_lock (gphoto2_backend->lock);
+ list = g_hash_table_lookup (gphoto2_backend->dir_name_cache, filename);
+ if (list == NULL)
+ {
+ g_mutex_unlock (gphoto2_backend->lock);
+ goto error_not_cached;
+ }
+ gp_list_ref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
+ for (n = 0; n < gp_list_count (list); n++)
+ {
+ gp_list_get_name (list, n, &name);
+ DEBUG (" try_enum folder '%s'", name);
+ info = g_file_info_new ();
+ if (!file_get_info (gphoto2_backend, filename, name, info, &error, TRUE))
+ {
+ g_mutex_lock (gphoto2_backend->lock);
+ gp_list_unref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
+ goto error_not_cached;
+ }
+ l = g_list_append (l, info);
}
+ g_mutex_lock (gphoto2_backend->lock);
gp_list_unref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
+
+ /* then list the files in each folder */
+ g_mutex_lock (gphoto2_backend->lock);
+ list = g_hash_table_lookup (gphoto2_backend->file_name_cache, filename);
+ if (list == NULL)
+ {
+ g_mutex_unlock (gphoto2_backend->lock);
+ goto error_not_cached;
+ }
+ gp_list_ref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
+ for (n = 0; n < gp_list_count (list); n++)
+ {
+ gp_list_get_name (list, n, &name);
+ DEBUG (" try_enum file '%s'", name);
+
+ info = g_file_info_new ();
+ if (!file_get_info (gphoto2_backend, filename, name, info, &error, TRUE))
+ {
+ g_mutex_lock (gphoto2_backend->lock);
+ gp_list_unref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
+ goto error_not_cached;
+ }
+ l = g_list_append (l, info);
+ }
+ g_mutex_lock (gphoto2_backend->lock);
+ gp_list_unref (list);
+ g_mutex_unlock (gphoto2_backend->lock);
/* and we're done */
@@ -1334,8 +2198,20 @@ do_enumerate (GVfsBackend *backend,
g_vfs_job_enumerate_done (job);
g_free (filename);
+ DEBUG (" YAY got info from cache for try_enumerate (%s)", given_filename);
+ return TRUE;
+
+ error_not_cached:
+ g_list_foreach (l, (GFunc) g_object_unref, NULL);
+ g_list_free (l);
+
+ g_free (filename);
+ DEBUG (" BUU no info from cache for try_enumerate (%s)", given_filename);
+ return FALSE;
}
+/* ------------------------------------------------------------------------------------------------- */
+
static void
do_query_fs_info (GVfsBackend *backend,
GVfsJobQueryFsInfo *job,
@@ -1345,45 +2221,1112 @@ do_query_fs_info (GVfsBackend *backend,
{
int rc;
GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ CameraStorageInformation *storage_info;
+ int num_storage_info;
+
+ DEBUG ("query_fs_info (%s)", filename);
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;
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, !gphoto2_backend->can_write);
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_mutex_lock (gphoto2_backend->lock);
+ gphoto2_backend->capacity = storage_info[0].capacitykbytes * 1024;
+ g_mutex_unlock (gphoto2_backend->lock);
g_file_info_set_attribute_uint64 (info,
G_FILE_ATTRIBUTE_FILESYSTEM_SIZE,
- storage_info[0].capacitykbytes * 1024);
+ (guint64) gphoto2_backend->capacity);
}
+
if (storage_info[0].fields & GP_STORAGEINFO_FREESPACEKBYTES)
{
+ g_mutex_lock (gphoto2_backend->lock);
+ gphoto2_backend->free_space = storage_info[0].freekbytes * 1024;
+ g_mutex_unlock (gphoto2_backend->lock);
g_file_info_set_attribute_uint64 (info,
G_FILE_ATTRIBUTE_FILESYSTEM_FREE,
- storage_info[0].freekbytes * 1024);
+ (guint64) gphoto2_backend->free_space);
}
-
}
- /*g_warning ("got %d storage_info objects", num_storage_info);*/
+ DEBUG (" got %d storage_info objects", num_storage_info);
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static gboolean
+try_query_fs_info (GVfsBackend *backend,
+ GVfsJobQueryFsInfo *job,
+ const char *filename,
+ GFileInfo *info,
+ GFileAttributeMatcher *attribute_matcher)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ gboolean ret;
+ gint64 free_space;
+ gint64 capacity;
+
+ DEBUG ("try_query_fs_info (%s)", filename);
+
+ ret = FALSE;
+
+ g_mutex_lock (gphoto2_backend->lock);
+ free_space = gphoto2_backend->free_space;
+ capacity = gphoto2_backend->capacity;
+ g_mutex_unlock (gphoto2_backend->lock);
+
+ if (free_space == -1 || capacity == -1)
+ {
+ DEBUG (" BUU no info from cache for try_query_fs_info (%s)", filename);
+ goto out;
+ }
+ DEBUG (" YAY got info from cache for try_query_fs_info (%s)", filename);
+
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "gphoto2");
+ g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_IF_LOCAL);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, !gphoto2_backend->can_write);
+ g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, (guint64) capacity);
+ g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, (guint64) free_space);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ ret = TRUE;
+ out:
+ return ret;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_make_directory (GVfsBackend *backend,
+ GVfsJobMakeDirectory *job,
+ const char *filename)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ char *name;
+ char *dir;
+ int rc;
+ GError *error;
+
+ DEBUG ("make_directory (%s)", filename);
+
+ ensure_not_dirty (gphoto2_backend);
+
+ dir = NULL;
+ name = NULL;
+ error = NULL;
+
+ if (!gphoto2_backend->can_write)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("Not supported"));
+ goto out;
+ }
+
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
+
+ rc = gp_camera_folder_make_dir (gphoto2_backend->camera,
+ dir,
+ name,
+ gphoto2_backend->context);
+ if (rc != 0)
+ {
+ error = get_error_from_gphoto2 (_I18N_LATER("Error creating directory"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ goto out;
+ }
+
+ caches_invalidate_dir (gphoto2_backend, dir);
+ caches_invalidate_free_space (gphoto2_backend);
+ monitors_emit_created (gphoto2_backend, dir, name);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (dir);
+ g_free (name);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static int
+do_slow_file_rename_in_same_dir (GVfsBackendGphoto2 *gphoto2_backend,
+ const char *dir,
+ const char *name,
+ const char *new_name,
+ gboolean allow_overwrite)
+{
+ int rc;
+ CameraFile *file;
+ CameraFile *file_dest;
+ const char *data;
+ unsigned long int size;
+
+ file = NULL;
+ file_dest = NULL;
+
+ DEBUG ("do_slow_file_rename_in_same_dir() '%s' '%s' -> '%s'", dir, name, new_name);
+
+ rc = gp_file_new (&file);
+ if (rc != 0)
+ goto out;
+
+ rc = gp_camera_file_get (gphoto2_backend->camera,
+ dir,
+ name,
+ GP_FILE_TYPE_NORMAL,
+ file,
+ gphoto2_backend->context);
+ if (rc != 0)
+ goto out;
+
+ rc = gp_file_get_data_and_size (file, &data, &size);
+ if (rc != 0)
+ goto out;
+
+ rc = gp_file_new (&file_dest);
+ if (rc != 0)
+ goto out;
+
+ rc = gp_file_copy (file_dest, file);
+ if (rc != 0)
+ goto out;
+
+ rc = gp_file_set_name (file_dest, new_name);
+ if (rc != 0)
+ goto out;
+
+ if (allow_overwrite)
+ {
+ gp_camera_file_delete (gphoto2_backend->camera,
+ dir,
+ new_name,
+ gphoto2_backend->context);
+ if (rc != 0)
+ {
+ DEBUG (" file delete failed as part of slow rename rc=%d", rc);
+ goto out;
+ }
}
+ rc = gp_camera_folder_put_file (gphoto2_backend->camera, dir, file_dest, gphoto2_backend->context);
+ if (rc != 0)
+ goto out;
+
+ rc = gp_camera_file_delete (gphoto2_backend->camera,
+ dir,
+ name,
+ gphoto2_backend->context);
+ if (rc != 0)
+ {
+ /* at least try to clean up the newly created file... */
+ gp_camera_file_delete (gphoto2_backend->camera,
+ dir,
+ new_name,
+ gphoto2_backend->context);
+ goto out;
+ }
+
+ out:
+ if (file != NULL)
+ gp_file_unref (file);
+ if (file_dest != NULL)
+ gp_file_unref (file_dest);
+ return rc;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static int
+do_file_rename_in_same_dir (GVfsBackendGphoto2 *gphoto2_backend,
+ const char *dir,
+ const char *name,
+ const char *new_name,
+ gboolean allow_overwrite)
+{
+ /* TODO: The libgphoto2 API speaks of just using
+ * gp_camera_file_set_info() to achieve this. However this
+ * fails on the devices that I own. So fall back to the slow
+ * method for now. Patches welcome for someone with a device
+ * where the above mentioned trick works.
+ */
+ return do_slow_file_rename_in_same_dir (gphoto2_backend, dir, name, new_name, allow_overwrite);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static int
+do_dir_rename_in_same_dir (GVfsBackendGphoto2 *gphoto2_backend,
+ const char *dir,
+ const char *name,
+ const char *new_name)
+{
+ int rc;
+ char *dir_name;
+
+ dir_name = g_build_filename (dir, name, NULL);
+
+ DEBUG ("do_dir_rename_in_same_dir() '%s' '%s' -> '%s' ('%s')", dir, name, new_name, dir_name);
+
+ /* TODO: Support non-empty folders by recursively renaming stuff.
+ * Or that might be too dangerous as it's not exactly atomic.
+ * And renaming files may be slow; see do_file_rename_in_same_dir() above.
+ */
+ if (is_directory_empty (gphoto2_backend, dir_name))
+ {
+ rc = gp_camera_folder_make_dir (gphoto2_backend->camera,
+ dir,
+ new_name,
+ gphoto2_backend->context);
+ if (rc != 0)
+ goto out;
+
+ rc = gp_camera_folder_remove_dir (gphoto2_backend->camera,
+ dir,
+ name,
+ gphoto2_backend->context);
+ if (rc != 0)
+ goto out;
+ }
+ else
+ {
+ rc = GP_ERROR_NOT_SUPPORTED;
+ }
+ out:
+ g_free (dir_name);
+ return rc;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_set_display_name (GVfsBackend *backend,
+ GVfsJobSetDisplayName *job,
+ const char *filename,
+ const char *display_name)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ char *name;
+ char *dir;
+ int rc;
+ char *dir_name;
+ GError *error;
+ char *new_name;
+
+ ensure_not_dirty (gphoto2_backend);
+
+ DEBUG ("set_display_name() '%s' -> '%s'", filename, display_name);
+
+ dir = NULL;
+ name = NULL;
+ dir_name = NULL;
+ new_name = NULL;
+
+ if (!gphoto2_backend->can_write)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("Not supported"));
+ goto out;
+ }
+
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
+
+ /* refuse is desired name is already taken */
+ if (is_directory (gphoto2_backend, dir, display_name) ||
+ is_regular (gphoto2_backend, dir, display_name))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_EXISTS,
+ _I18N_LATER("Name already exists"));
+ goto out;
+ }
+
+ /* ensure name is not too long - otherwise it might screw up enumerating
+ * the folder on some devices
+ */
+ if (strlen (display_name) > 63)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("New name too long"));
+ goto out;
+ }
+
+ if (is_directory (gphoto2_backend, dir, name))
+ {
+ /* dir renaming */
+ rc = do_dir_rename_in_same_dir (gphoto2_backend, dir, name, display_name);
+ if (rc != 0)
+ {
+ error = get_error_from_gphoto2 (_I18N_LATER("Error renaming dir"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ goto out;
+ }
+ caches_invalidate_file (gphoto2_backend, dir, name);
+ }
+ else
+ {
+ /* file renaming */
+ rc = do_file_rename_in_same_dir (gphoto2_backend, dir, name, display_name, FALSE);
+ if (rc != 0)
+ {
+ error = get_error_from_gphoto2 (_I18N_LATER("Error renaming file"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ goto out;
+ }
+ caches_invalidate_file (gphoto2_backend, dir, name);
+ }
+
+
+ /* emit on monitor */
+ monitors_emit_deleted (gphoto2_backend, dir, name);
+ monitors_emit_created (gphoto2_backend, dir, display_name);
+
+ new_name = g_build_filename (dir + strlen (gphoto2_backend->ignore_prefix), display_name, NULL);
+ g_vfs_job_set_display_name_set_new_path (job, new_name);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (dir);
+ g_free (name);
+ g_free (dir_name);
+ g_free (new_name);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_delete (GVfsBackend *backend,
+ GVfsJobDelete *job,
+ const char *filename)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ char *name;
+ char *dir;
+ int rc;
+ GError *error;
+ char *dir_name;
+
+ ensure_not_dirty (gphoto2_backend);
+
+ DEBUG ("delete() '%s'", filename);
+
+ dir = NULL;
+ name = NULL;
+ dir_name = NULL;
+
+ if (!gphoto2_backend->can_write)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("Not supported"));
+ goto out;
+ }
+
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
+
+ if (is_directory (gphoto2_backend, dir, name))
+ {
+ dir_name = add_ignore_prefix (gphoto2_backend, filename);
+ if (!is_directory_empty (gphoto2_backend, dir_name))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_EMPTY,
+ _I18N_LATER("Directory '%s' is not empty"), filename);
+ goto out;
+ }
+ else
+ {
+ rc = gp_camera_folder_remove_dir (gphoto2_backend->camera,
+ dir,
+ name,
+ gphoto2_backend->context);
+ if (rc != 0)
+ {
+ error = get_error_from_gphoto2 (_I18N_LATER("Error deleting directory"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ goto out;
+ }
+ caches_invalidate_file (gphoto2_backend, dir, name);
+ caches_invalidate_free_space (gphoto2_backend);
+ monitors_emit_deleted (gphoto2_backend, dir, name);
+ }
+ }
+ else
+ {
+ if (!is_regular (gphoto2_backend, dir, name))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ _I18N_LATER("No such file or directory"));
+ goto out;
+ }
+
+ rc = gp_camera_file_delete (gphoto2_backend->camera,
+ dir,
+ name,
+ gphoto2_backend->context);
+ if (rc != 0)
+ {
+ error = get_error_from_gphoto2 (_I18N_LATER("Error deleting file"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ goto out;
+ }
+
+ caches_invalidate_file (gphoto2_backend, dir, name);
+ caches_invalidate_free_space (gphoto2_backend);
+ monitors_emit_deleted (gphoto2_backend, dir, name);
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (dir);
+ g_free (name);
+ g_free (dir_name);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_create_internal (GVfsBackend *backend,
+ GVfsJobOpenForWrite *job,
+ const char *filename,
+ GFileCreateFlags flags,
+ gboolean job_is_replace,
+ gboolean job_is_append_to)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ WriteHandle *handle;
+ char *dir;
+ char *name;
+
+ ensure_not_dirty (gphoto2_backend);
+
+ dir = NULL;
+ name = NULL;
+
+ if (!gphoto2_backend->can_write)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("Not supported"));
+ goto out;
+ }
+
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
+
+ if (is_directory (gphoto2_backend, dir, name))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_IS_DIRECTORY,
+ _I18N_LATER("Can't write to directory"));
+ goto out;
+ }
+
+ /* unless we're replacing or appending.. error out if file already exists */
+ if (is_regular (gphoto2_backend, dir, name))
+ {
+ if (! (job_is_replace || job_is_append_to))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_EXISTS,
+ _I18N_LATER("File exists"));
+ goto out;
+ }
+ }
+ else
+ {
+ if (job_is_replace || job_is_append_to)
+ {
+ /* so we're not really replacing or appending; dont fail these
+ * operations; just turn them into create instead...
+ */
+ job_is_replace = FALSE;
+ job_is_append_to = FALSE;
+ }
+ }
+
+ handle = g_new0 (WriteHandle, 1);
+ handle->filename = g_strdup (filename);
+ handle->dir = g_strdup (dir);
+ handle->name = g_strdup (name);
+ handle->job_is_replace = job_is_replace;
+ handle->job_is_append_to = job_is_append_to;
+ handle->is_dirty = TRUE;
+
+ /* if we're appending to a file read in all of the file to memory */
+ if (job_is_append_to)
+ {
+ int rc;
+ GError *error;
+ CameraFile *file;
+ const char *data;
+ unsigned long int size;
+
+ rc = gp_file_new (&file);
+ if (rc != 0)
+ {
+ error = get_error_from_gphoto2 (_I18N_LATER("Cannot allocate new file to append to"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ write_handle_free (handle);
+ goto out;
+ }
+
+ rc = gp_camera_file_get (gphoto2_backend->camera,
+ dir,
+ name,
+ GP_FILE_TYPE_NORMAL,
+ file,
+ gphoto2_backend->context);
+ if (rc != 0)
+ {
+ error = get_error_from_gphoto2 (_I18N_LATER("Cannot read file to append to"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ write_handle_free (handle);
+ gp_file_unref (file);
+ goto out;
+ }
+
+ rc = gp_file_get_data_and_size (file, &data, &size);
+ if (rc != 0)
+ {
+ error = get_error_from_gphoto2 (_I18N_LATER("Cannot get data of file to append to"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ write_handle_free (handle);
+ gp_file_unref (file);
+ goto out;
+ }
+
+ handle->data = g_malloc (size + WRITE_INCREMENT);
+ handle->allocated_size = size + WRITE_INCREMENT;
+ handle->size = size;
+ handle->cursor = size;
+ memcpy (handle->data, data, size);
+ gp_file_unref (file);
+
+ }
+ else
+ {
+ handle->data = g_malloc (WRITE_INCREMENT);
+ handle->allocated_size = WRITE_INCREMENT;
+ }
+
+ g_vfs_job_open_for_write_set_handle (job, handle);
+ g_vfs_job_open_for_write_set_can_seek (job, TRUE);
+
+ gphoto2_backend->open_write_handles = g_list_prepend (gphoto2_backend->open_write_handles, handle);
+
+ DEBUG (" handle=%p", handle);
+
+ /* make sure we invalidate the dir and the file */
+ caches_invalidate_file (gphoto2_backend, dir, name);
+
+ /* emit on the monitor - hopefully some client won't need info
+ * about this (to avoid committing dirty bits midwrite) before
+ * the write is done...
+ */
+ if (job_is_replace || job_is_append_to)
+ monitors_emit_changed (gphoto2_backend, dir, name);
+ else
+ monitors_emit_created (gphoto2_backend, dir, name);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (dir);
+ g_free (name);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_create (GVfsBackend *backend,
+ GVfsJobOpenForWrite *job,
+ const char *filename,
+ GFileCreateFlags flags)
+{
+ DEBUG ("create() '%s' flags=0x%04x", filename, flags);
+
+ return do_create_internal (backend, job, filename, flags, FALSE, FALSE);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_replace (GVfsBackend *backend,
+ GVfsJobOpenForWrite *job,
+ const char *filename,
+ const char *etag,
+ gboolean make_backup,
+ GFileCreateFlags flags)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ char *dir;
+ char *name;
+
+ DEBUG ("replace() '%s' etag='%s' make_backup=%d flags=0x%04x", filename, etag, make_backup, flags);
+
+ dir = NULL;
+ name = NULL;
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
+
+ /* write a new file
+ * - will delete the existing one when done in do_close_write()
+ */
+ do_create_internal (backend, job, filename, flags, TRUE, FALSE);
+
+ g_free (dir);
+ g_free (name);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_append_to (GVfsBackend *backend,
+ GVfsJobOpenForWrite *job,
+ const char *filename,
+ GFileCreateFlags flags)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ char *dir;
+ char *name;
+
+ DEBUG ("append_to() '%s' flags=0x%04x", filename, flags);
+
+ dir = NULL;
+ name = NULL;
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
+
+ /* write a new file
+ * - will read existing data in do_create_internal
+ * - will delete the existing one when done in do_close_write()
+ */
+ do_create_internal (backend, job, filename, flags, FALSE, TRUE);
+
+ g_free (dir);
+ g_free (name);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_write (GVfsBackend *backend,
+ GVfsJobWrite *job,
+ GVfsBackendHandle _handle,
+ char *buffer,
+ gsize buffer_size)
+{
+ WriteHandle *handle = _handle;
+
+ DEBUG ("write() %p, '%s', %d bytes", handle, handle->filename, buffer_size);
+
+ /* ensure we have enough room */
+ if (handle->cursor + buffer_size > handle->allocated_size)
+ {
+ unsigned long int new_allocated_size;
+ new_allocated_size = ((handle->cursor + buffer_size) / WRITE_INCREMENT + 1) * WRITE_INCREMENT;
+ handle->data = g_realloc (handle->data, new_allocated_size);
+ handle->allocated_size = new_allocated_size;
+ DEBUG (" allocated_size is now %ld bytes)", handle->allocated_size);
+ }
+
+
+ memcpy (handle->data + handle->cursor, buffer, buffer_size);
+ handle->cursor += buffer_size;
+
+ if (handle->cursor > handle->size)
+ handle->size = handle->cursor;
+
+ /* this will make us dirty */
+ handle->is_dirty = TRUE;
+
+ g_vfs_job_write_set_written_size (job, buffer_size);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_seek_on_write (GVfsBackend *backend,
+ GVfsJobSeekWrite *job,
+ GVfsBackendHandle handle,
+ goffset offset,
+ GSeekType type)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ WriteHandle *write_handle = handle;
+ long new_offset;
+
+ DEBUG ("seek_on_write() %p '%s' offset=%d type=%d cursor=%ld size=%ld", write_handle, write_handle->filename, (int)offset, type, write_handle->cursor, write_handle->size);
+
+ switch (type)
+ {
+ default:
+ case G_SEEK_SET:
+ new_offset = offset;
+ break;
+ case G_SEEK_CUR:
+ new_offset = write_handle->cursor + offset;
+ break;
+ case G_SEEK_END:
+ new_offset = write_handle->size + offset;
+ break;
+ }
+
+ if (new_offset < 0 || new_offset > write_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
+ {
+ write_handle->cursor = new_offset;
+ g_vfs_job_seek_write_set_offset (job, offset);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+ }
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+/* this functions updates the device with the data currently in write_handle */
+static int
+commit_write_handle (GVfsBackendGphoto2 *gphoto2_backend, WriteHandle *write_handle)
+{
+ int rc;
+ CameraFile *file;
+
+ DEBUG ("commit_write_handle() '%s' of size %ld", write_handle->filename, write_handle->size);
+
+ /* no need to write as we're not dirty */
+ if (!write_handle->is_dirty)
+ {
+ DEBUG (" not dirty => not writing");
+ return 0;
+ }
+
+ if (write_handle->delete_before ||
+ (write_handle->job_is_replace || write_handle->job_is_append_to))
+ {
+ /* OK, so this is not atomic. But there's no way we can make it
+ * atomic until rename works properly - see comments in
+ * do_set_display_name() and why have do_slow_rename()...
+ *
+ * So first delete the existing file...
+ */
+ rc = gp_camera_file_delete (gphoto2_backend->camera,
+ write_handle->dir,
+ write_handle->name,
+ gphoto2_backend->context);
+ if (rc != 0)
+ goto out;
+
+ DEBUG (" deleted '%s' '%s' for delete_before=%d, job_is_replace=%d, job_is_append_to=%d",
+ write_handle->dir, write_handle->name,
+ write_handle->delete_before, write_handle->job_is_replace, write_handle->job_is_append_to);
+ }
+
+ rc = gp_file_new (&file);
+ if (rc != 0)
+ goto out;
+
+ gp_file_set_type (file, GP_FILE_TYPE_NORMAL);
+ gp_file_set_name (file, write_handle->name);
+ gp_file_set_mtime (file, time (NULL));
+ gp_file_set_data_and_size (file,
+ dup_for_gphoto2 (write_handle->data, write_handle->size),
+ write_handle->size);
+
+ rc = gp_camera_folder_put_file (gphoto2_backend->camera, write_handle->dir, file, gphoto2_backend->context);
+ if (rc != 0)
+ {
+ gp_file_unref (file);
+ goto out;
+ }
+
+ DEBUG (" successfully wrote '%s' of %ld bytes", write_handle->filename, write_handle->size);
+ monitors_emit_changed (gphoto2_backend, write_handle->dir, write_handle->name);
+
+ gp_file_unref (file);
+
+ out:
+ write_handle->is_dirty = FALSE;
+ write_handle->delete_before = TRUE;
+
+ caches_invalidate_file (gphoto2_backend, write_handle->dir, write_handle->name);
+ caches_invalidate_free_space (gphoto2_backend);
+
+ return rc;
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_close_write (GVfsBackend *backend,
+ GVfsJobCloseWrite *job,
+ GVfsBackendHandle handle)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ WriteHandle *write_handle = handle;
+ GError *error;
+ int rc;
+
+ DEBUG ("close_write() %p '%s' %ld bytes total", write_handle, write_handle->filename, write_handle->size);
+
+ rc = commit_write_handle (gphoto2_backend, write_handle);
+ if (rc != 0)
+ {
+ error = get_error_from_gphoto2 (_I18N_LATER("Error writing file"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ goto out;
+ }
+
+ monitors_emit_changed (gphoto2_backend, write_handle->dir, write_handle->name);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ write_handle_free (write_handle);
+ gphoto2_backend->open_write_handles = g_list_remove (gphoto2_backend->open_write_handles, write_handle);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+do_move (GVfsBackend *backend,
+ GVfsJobMove *job,
+ const char *source,
+ const char *destination,
+ GFileCopyFlags flags,
+ GFileProgressCallback progress_callback,
+ gpointer progress_callback_data)
+{
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ char *src_dir;
+ char *src_name;
+ char *dst_dir;
+ char *dst_name;
+ int rc;
+ GError *error;
+ gboolean mv_dir;
+
+ DEBUG ("move() '%s' -> '%s' %04x)", source, destination, flags);
+
+ ensure_not_dirty (gphoto2_backend);
+
+ split_filename_with_ignore_prefix (gphoto2_backend, source, &src_dir, &src_name);
+ split_filename_with_ignore_prefix (gphoto2_backend, destination, &dst_dir, &dst_name);
+
+ /* this is an limited implementation that can only move files / folders in the same directory */
+ if (strcmp (src_dir, dst_dir) != 0)
+ {
+ DEBUG (" not supported (not same directory)");
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("Not supported (not same directory)"));
+ goto out;
+ }
+
+ mv_dir = FALSE;
+ if (is_directory (gphoto2_backend, src_dir, src_name))
+ {
+ if (is_directory (gphoto2_backend, dst_dir, dst_name))
+ {
+ DEBUG (" not supported (src is dir; dst is dir)");
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("Not supported (src is dir, dst is dir)"));
+ goto out;
+ }
+ else if (is_regular (gphoto2_backend, dst_dir, dst_name))
+ {
+ DEBUG (" not supported (src is dir; dst is existing file)");
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("Not supported (src is dir, dst is existing file)"));
+ goto out;
+ }
+ mv_dir = TRUE;
+ }
+ else
+ {
+ if (is_directory (gphoto2_backend, dst_dir, dst_name))
+ {
+ DEBUG (" not supported (src is file; dst is dir)");
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("Not supported (src is file, dst is dir)"));
+ goto out;
+ }
+ }
+
+ /* ensure name is not too long - otherwise it might screw up enumerating
+ * the folder on some devices
+ */
+ if (strlen (dst_name) > 63)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _I18N_LATER("New name too long"));
+ goto out;
+ }
+
+ if (mv_dir)
+ {
+ DEBUG (" renaming dir");
+ rc = do_dir_rename_in_same_dir (gphoto2_backend, src_dir, src_name, dst_name);
+ if (rc != 0)
+ {
+ DEBUG (" error renaming dir");
+ error = get_error_from_gphoto2 (_I18N_LATER("Error renaming dir"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ goto out;
+ }
+ }
+ else
+ {
+ DEBUG (" renaming file");
+ rc = do_file_rename_in_same_dir (gphoto2_backend, src_dir, src_name, dst_name, flags & G_FILE_COPY_OVERWRITE);
+ if (rc != 0)
+ {
+ DEBUG (" error renaming file");
+ error = get_error_from_gphoto2 (_I18N_LATER("Error renaming file"), rc);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ goto out;
+ }
+ }
+
+ caches_invalidate_file (gphoto2_backend, src_dir, src_name);
+ monitors_emit_deleted (gphoto2_backend, src_dir, src_name);
+ monitors_emit_created (gphoto2_backend, src_dir, dst_name);
+
+ DEBUG (" success");
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (src_dir);
+ g_free (src_name);
+ g_free (dst_dir);
+ g_free (dst_name);
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+vfs_dir_monitor_destroyed (gpointer user_data, GObject *where_the_object_was)
+{
+ GList *l;
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (user_data);
+
+ DEBUG ("vfs_dir_monitor_destroyed()");
+
+ for (l = gphoto2_backend->dir_monitor_proxies; l != NULL; l = l->next)
+ {
+ MonitorProxy *proxy = l->data;
+ if (G_OBJECT (proxy->vfs_monitor) == where_the_object_was)
+ {
+ gphoto2_backend->dir_monitor_proxies = g_list_remove (gphoto2_backend->dir_monitor_proxies, proxy);
+ DEBUG (" Removed dead dir monitor for '%s'", proxy->path);
+ monitor_proxy_free (proxy);
+ break;
+ }
+ }
+}
+
+static void
+do_create_dir_monitor (GVfsBackend *backend,
+ GVfsJobCreateMonitor *job,
+ const char *filename,
+ GFileMonitorFlags flags)
+{
+ char *dir;
+ char *name;
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ MonitorProxy *proxy;
+
+ DEBUG ("create_dir_monitor (%s)", filename);
+
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
+
+ proxy = g_new0 (MonitorProxy, 1);
+ proxy->path = add_ignore_prefix (gphoto2_backend, filename);
+ proxy->vfs_monitor = g_vfs_monitor_new (backend);
+
+ gphoto2_backend->dir_monitor_proxies = g_list_prepend (gphoto2_backend->dir_monitor_proxies, proxy);
+
+ g_vfs_job_create_monitor_set_monitor (job, proxy->vfs_monitor);
+ g_object_weak_ref (G_OBJECT (proxy->vfs_monitor), vfs_dir_monitor_destroyed, gphoto2_backend);
+ g_object_unref (proxy->vfs_monitor);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+}
+
+/* ------------------------------------------------------------------------------------------------- */
+
+static void
+vfs_file_monitor_destroyed (gpointer user_data, GObject *where_the_object_was)
+{
+ GList *l;
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (user_data);
+
+ DEBUG ("vfs_file_monitor_destroyed()");
+
+ for (l = gphoto2_backend->file_monitor_proxies; l != NULL; l = l->next)
+ {
+ MonitorProxy *proxy = l->data;
+ if (G_OBJECT (proxy->vfs_monitor) == where_the_object_was)
+ {
+ gphoto2_backend->dir_monitor_proxies = g_list_remove (gphoto2_backend->dir_monitor_proxies, proxy);
+ DEBUG (" Removed dead file monitor for '%s'", proxy->path);
+ monitor_proxy_free (proxy);
+ break;
+ }
+ }
+}
+
+static void
+do_create_file_monitor (GVfsBackend *backend,
+ GVfsJobCreateMonitor *job,
+ const char *filename,
+ GFileMonitorFlags flags)
+{
+ char *dir;
+ char *name;
+ GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend);
+ MonitorProxy *proxy;
+
+ DEBUG ("create_file_monitor (%s)", filename);
+
+ split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name);
+
+ proxy = g_new0 (MonitorProxy, 1);
+ proxy->path = add_ignore_prefix (gphoto2_backend, filename);
+ proxy->vfs_monitor = g_vfs_monitor_new (backend);
+
+ gphoto2_backend->file_monitor_proxies = g_list_prepend (gphoto2_backend->file_monitor_proxies, proxy);
+
+ g_vfs_job_create_monitor_set_monitor (job, proxy->vfs_monitor);
+ g_object_weak_ref (G_OBJECT (proxy->vfs_monitor), vfs_file_monitor_destroyed, gphoto2_backend);
+ g_object_unref (proxy->vfs_monitor);
g_vfs_job_succeeded (G_VFS_JOB (job));
}
+/* ------------------------------------------------------------------------------------------------- */
+
static void
g_vfs_backend_gphoto2_class_init (GVfsBackendGphoto2Class *klass)
{
@@ -1396,10 +3339,27 @@ g_vfs_backend_gphoto2_class_init (GVfsBackendGphoto2Class *klass)
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->try_read = try_read;
+ backend_class->try_seek_on_read = try_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;
+ backend_class->make_directory = do_make_directory;
+ backend_class->set_display_name = do_set_display_name;
+ backend_class->delete = do_delete;
+ backend_class->create = do_create;
+ backend_class->replace = do_replace;
+ backend_class->append_to = do_append_to;
+ backend_class->write = do_write;
+ backend_class->close_write = do_close_write;
+ backend_class->seek_on_write = do_seek_on_write;
+ backend_class->move = do_move;
+ backend_class->create_dir_monitor = do_create_dir_monitor;
+ backend_class->create_file_monitor = do_create_file_monitor;
+
+ /* fast sync versions that only succeed if info is in the cache */
+ backend_class->try_query_info = try_query_info;
+ backend_class->try_enumerate = try_enumerate;
+ backend_class->try_query_fs_info = try_query_fs_info;
}