diff options
author | David Zeuthen <davidz@redhat.com> | 2008-03-03 19:36:51 +0000 |
---|---|---|
committer | David Zeuthen <davidz@src.gnome.org> | 2008-03-03 19:36:51 +0000 |
commit | 958b2491f3f400517dfa0fdacf23639d1ca67937 (patch) | |
tree | 7e642f1634e61e913b601335d9a53b33ef128603 | |
parent | f3c25f5b0370013f99d2dba907629ff37c29a7b6 (diff) | |
download | gvfs-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-- | ChangeLog | 30 | ||||
-rw-r--r-- | daemon/gvfsbackendgphoto2.c | 2774 |
2 files changed, 2397 insertions, 407 deletions
@@ -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; } |