/* GVFS gphoto2 file system driver * * 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 * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: David Zeuthen */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gvfsgphoto2utils.h" #include "gvfsbackendgphoto2.h" #include "gvfsjobopenforread.h" #include "gvfsjobopeniconforread.h" #include "gvfsjobread.h" #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 "gvfsjobunmount.h" #include "gvfsmonitor.h" #include "gvfsjobseekwrite.h" #include "gvfsicon.h" /* use this to disable caching */ #if 0 #define DEBUG_NO_CACHING 1 #endif /*--------------------------------------------------------------------------------------------------------------*/ /* TODO: * * - write support * - 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 * * - adding a payload cache don't make much sense as libgphoto2 has a LRU cache already * - (see comment in the do_close_write() function) * * - Support PTP/IP devices nicely * - Need hardware for testing * - Should actually work out of the box; just try mounting e.g. * gphoto2://[ptpip: GFileInfo */ GHashTable *info_cache; /* dir name -> CameraList of (sub-) directory names in given directory */ GHashTable *dir_name_cache; /* dir name -> CameraList of file names in given directory */ GHashTable *file_name_cache; /* monitors (only used on the IO thread) */ GList *dir_monitor_proxies; GList *file_monitor_proxies; /* list of open read handles (only used on the IO thread) */ GList *open_read_handles; /* 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; /* ------------------------------------------------------------------------------------------------- */ typedef struct { goffset size; float target; GFileProgressCallback progress_callback; gpointer progress_callback_data; } PullContext; /* ------------------------------------------------------------------------------------------------- */ 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; g_debug ("ensure_not_dirty: looking at handle for '%s'\n", 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)); g_debug ("monitors_emit_internal() %s for '%s' '%s'\n", 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); g_debug (" emitted %s for '%s' on dir monitor for '%s'\n", 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); g_debug (" emitted %s for '%s' on file monitor\n", 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) { g_debug ("monitors_emit_created(): '%s' '%s'\n", 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) { g_debug ("monitors_emit_deleted(): '%s' '%s'\n", 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) { g_debug ("monitors_emit_changed(): '%s' '%s'\n", dir, name); monitors_emit_internal (gphoto2_backend, dir, name, G_FILE_MONITOR_EVENT_CHANGED, "CHANGED"); } /* ------------------------------------------------------------------------------------------------- */ static void caches_invalidate_all (GVfsBackendGphoto2 *gphoto2_backend) { g_debug ("caches_invalidate_all()\n"); 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) { g_debug ("caches_invalidate_dir() for '%s'\n", 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); g_debug ("caches_invalidate_file() for '%s'\n", full_name); g_free (full_name); } /* ------------------------------------------------------------------------------------------------- */ static GError * get_error_from_gphoto2 (const char *message, int rc) { GError *error; switch (rc) { case GP_ERROR_FILE_EXISTS: case GP_ERROR_DIRECTORY_EXISTS: error = g_error_new (G_IO_ERROR, /* Translator: %s represents a more specific error message and %d the specific error code */ G_IO_ERROR_EXISTS, _("%s: %d: Directory or file exists"), message, rc); break; case GP_ERROR_FILE_NOT_FOUND: case GP_ERROR_DIRECTORY_NOT_FOUND: error = g_error_new (G_IO_ERROR, /* Translator: %s represents a more specific error message and %d the specific error code */ G_IO_ERROR_NOT_FOUND, _("%s: %d: No such file or directory"), message, rc); break; case GP_ERROR_PATH_NOT_ABSOLUTE: error = g_error_new (G_IO_ERROR, /* Translator: %s represents a more specific error message and %d the specific error code */ G_IO_ERROR_INVALID_FILENAME, _("%s: %d: Invalid filename"), message, rc); break; case GP_ERROR_NOT_SUPPORTED: error = g_error_new (G_IO_ERROR, /* Translator: %s represents a more specific error message and %d the specific error code */ G_IO_ERROR_NOT_SUPPORTED, _("%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; if (gphoto2_backend->context != NULL) { gp_context_unref (gphoto2_backend->context); gphoto2_backend->context = NULL; } if (gphoto2_backend->camera != NULL) { gp_camera_unref (gphoto2_backend->camera); gphoto2_backend->camera = NULL; } if (gphoto2_backend->gudev_client != NULL) g_object_unref (gphoto2_backend->gudev_client); if (gphoto2_backend->udev_device != NULL) g_object_unref (gphoto2_backend->udev_device); g_free (gphoto2_backend->icon_name); gphoto2_backend->icon_name = NULL; g_free (gphoto2_backend->symbolic_icon_name); gphoto2_backend->symbolic_icon_name = NULL; g_free (gphoto2_backend->ignore_prefix); gphoto2_backend->ignore_prefix = NULL; if (gphoto2_backend->info_cache != NULL) { g_hash_table_unref (gphoto2_backend->info_cache); gphoto2_backend->info_cache = NULL; } if (gphoto2_backend->dir_name_cache != NULL) { g_hash_table_unref (gphoto2_backend->dir_name_cache); gphoto2_backend->dir_name_cache = NULL; } if (gphoto2_backend->file_name_cache != NULL) { g_hash_table_unref (gphoto2_backend->file_name_cache); gphoto2_backend->file_name_cache = NULL; } 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; 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_debug ("finalizing %p\n", object); release_device (gphoto2_backend); g_mutex_clear (&gphoto2_backend->lock); if (G_OBJECT_CLASS (g_vfs_backend_gphoto2_parent_class)->finalize) (*G_OBJECT_CLASS (g_vfs_backend_gphoto2_parent_class)->finalize) (object); } /* ------------------------------------------------------------------------------------------------- */ static void _gphoto2_logger_func (GPLogLevel level, const char *domain, const char *str, void *data) { g_print ("%s: %s\n", domain, str); } static void g_vfs_backend_gphoto2_init (GVfsBackendGphoto2 *gphoto2_backend) { GVfsBackend *backend = G_VFS_BACKEND (gphoto2_backend); GMountSpec *mount_spec; const char *debug; GPLogLevel level = -1; g_debug ("initing %p\n", gphoto2_backend); g_mutex_init (&gphoto2_backend->lock); 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); debug = g_getenv ("GVFS_GPHOTO2_DEBUG"); if (debug) { if (g_ascii_strcasecmp ("all", debug) == 0 || g_ascii_strcasecmp ("data", debug) == 0) level = GP_LOG_DATA; else if (g_ascii_strcasecmp ("debug", debug) == 0) level = GP_LOG_DEBUG; else if (g_ascii_strcasecmp ("verbose", debug) == 0) level = GP_LOG_VERBOSE; else level = GP_LOG_ERROR; gp_log_add_func (level, _gphoto2_logger_func, NULL); } } /* ------------------------------------------------------------------------------------------------- */ static char * compute_icon_name (GVfsBackendGphoto2 *gphoto2_backend) { char *result; if (gphoto2_backend->icon_name == NULL) { result = g_strdup_printf ("camera-photo"); } else { result = g_strdup (gphoto2_backend->icon_name); } return result; } static char * compute_symbolic_icon_name (GVfsBackendGphoto2 *gphoto2_backend) { char *result; if (gphoto2_backend->symbolic_icon_name == NULL) { result = g_strdup_printf ("camera-photo-symbolic"); } else { result = g_strdup (gphoto2_backend->symbolic_icon_name); } return result; } /* ------------------------------------------------------------------------------------------------- */ static char * compute_display_name (GVfsBackendGphoto2 *gphoto2_backend) { char *result = NULL; if (gphoto2_backend->udev_device != NULL) result = g_vfs_get_volume_name (gphoto2_backend->udev_device, "ID_GPHOTO2"); if (result == NULL ) { /* Translator: %s represents the device, e.g. usb:001,042 */ result = g_strdup_printf (_("Digital Camera (%s)"), gphoto2_backend->gphoto2_port); } return result; } /* ------------------------------------------------------------------------------------------------- */ static void setup_for_device (GVfsBackendGphoto2 *gphoto2_backend) { gchar *devname; char *comma; /* turn usb:001,041 string into an udev device name */ if (!g_str_has_prefix (gphoto2_backend->gphoto2_port, "usb:")) return; devname = g_strconcat ("/dev/bus/usb/", gphoto2_backend->gphoto2_port+4, NULL); if ((comma = strchr (devname, ',')) == NULL) { g_free (devname); return; } *comma = '/'; g_debug ("Parsed '%s' into device name %s\n", gphoto2_backend->gphoto2_port, devname); /* find corresponding GUdevDevice */ gphoto2_backend->udev_device = g_udev_client_query_by_device_file (gphoto2_backend->gudev_client, devname); g_free (devname); if (gphoto2_backend->udev_device) { g_debug ("-> sysfs path %s, subsys %s, name %s\n", g_udev_device_get_sysfs_path (gphoto2_backend->udev_device), g_udev_device_get_subsystem (gphoto2_backend->udev_device), g_udev_device_get_name (gphoto2_backend->udev_device)); gphoto2_backend->icon_name = g_vfs_get_volume_icon (gphoto2_backend->udev_device); gphoto2_backend->symbolic_icon_name = g_vfs_get_volume_symbolic_icon (gphoto2_backend->udev_device); } else g_debug ("-> did not find matching udev device\n"); g_vfs_backend_set_x_content_types (G_VFS_BACKEND (gphoto2_backend), g_vfs_get_x_content_types (gphoto2_backend->udev_device)); } static void on_uevent (GUdevClient *client, gchar *action, GUdevDevice *device, gpointer user_data) { GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (user_data); g_debug ("on_uevent action %s, device %s\n", action, g_udev_device_get_device_file (device)); if (gphoto2_backend->udev_device != NULL && g_strcmp0 (g_udev_device_get_device_file (gphoto2_backend->udev_device), g_udev_device_get_device_file (device)) == 0 && strcmp (action, "remove") == 0) { g_debug ("we have been removed!\n"); /* nuke all caches so we're a bit more valgrind friendly */ caches_invalidate_all (gphoto2_backend); g_vfs_backend_force_unmount (G_VFS_BACKEND (gphoto2_backend)); g_signal_handlers_disconnect_by_func (gphoto2_backend->gudev_client, on_uevent, gphoto2_backend); } } /* ------------------------------------------------------------------------------------------------- */ 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'; /*g_debug ("split_filename_with_ignore_prefix: '%s' -> '%s' '%s'\n", 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); /*g_debug ("add_ignore_prefix: '%s' -> '%s'\n", 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); g_debug ("file_get_info() try_cache_only=%d dir='%s', name='%s'\n" " full_path='%s' ignore_prefix='%s'\n", 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); g_debug (" Using cached info %p for '%s'\n", 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); g_debug (" No cached info for '%s'\n", 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); icon = g_themed_icon_new ("folder-symbolic"); g_file_info_set_symbolic_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_delete); 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; g_debug (" Generating info (root folder) for '%s'\n", 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); icon = g_themed_icon_new ("folder-symbolic"); g_file_info_set_symbolic_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_delete); 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; g_debug (" Generating info (folder) for '%s'\n", 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, _("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); g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, mime_type); /* we offer thumbnails for both pics and video (see bgo #585853) */ if (g_str_has_prefix (mime_type, "image") || g_str_has_prefix (mime_type, "video")) { char *icon_id; GIcon *icon; GMountSpec *mount_spec; mount_spec = g_vfs_backend_get_mount_spec (G_VFS_BACKEND (gphoto2_backend)); icon_id = g_strdup_printf ("preview:%s/%s", dir + strlen (gphoto2_backend->ignore_prefix), name); icon = g_vfs_icon_new (mount_spec, icon_id); g_file_info_set_attribute_object (info, G_FILE_ATTRIBUTE_PREVIEW_ICON, G_OBJECT (icon)); g_object_unref (icon); g_free (icon_id); } icon = g_content_type_get_icon (mime_type); g_debug (" got icon %p for mime_type '%s'\n", icon, mime_type); if (icon != NULL) { g_file_info_set_icon (info, icon); g_object_unref (icon); } icon = g_content_type_get_symbolic_icon (mime_type); g_debug (" got symbolic icon %p for mime_type '%s'\n", icon, mime_type); if (icon != NULL) { g_file_info_set_symbolic_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_delete); 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; g_debug (" Generating info (file) for '%s'\n", 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); g_debug (" Storing cached info %p for '%s'\n", 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; g_debug ("is_directory_empty begin (%s)\n", 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: g_debug (" is_directory_empty (%s) -> %d\n", dir, ret); return ret; } /* ------------------------------------------------------------------------------------------------- */ /* If we only have a single storage head, the gphoto2 volume monitor * will not use activation roots into our mount. This is mainly to * work around buggy devices where the basedir of the storage head * changes on every camera initialization, e.g. the iPhone. * * So, if we have only one storage head, do use basedir of that * head as ignore_prefix. * * See also update_cameras() in ggphoto2volumemonitor.c. * * This function needs to be called from do_mount(). */ static gboolean ensure_ignore_prefix (GVfsBackendGphoto2 *gphoto2_backend, GVfsJob *job) { gchar *prefix; CameraStorageInformation *storage_info, *head; int num_storage_info, i; /* already set */ if (gphoto2_backend->ignore_prefix != NULL) return TRUE; prefix = NULL; if (gp_camera_get_storageinfo (gphoto2_backend->camera, &storage_info, &num_storage_info, gphoto2_backend->context) != 0) goto out; head = NULL; for (i = 0; i < num_storage_info; i++) { /* Ignore storage with no capacity (see bug 570888) */ if ((storage_info[i].fields & GP_STORAGEINFO_MAXCAPACITY) && storage_info[i].capacitykbytes == 0) continue; /* Multiple heads, don't ignore */ if (head != NULL) goto out; head = &storage_info[i]; } /* Some cameras, such as the Canon 5D, won't report the basedir */ if (head && head->fields & GP_STORAGEINFO_BASE) prefix = g_strdup_printf ("%s/", head->basedir); out: if (prefix == NULL) gphoto2_backend->ignore_prefix = g_strdup ("/"); else gphoto2_backend->ignore_prefix = prefix; g_debug ("Using ignore_prefix='%s'\n", gphoto2_backend->ignore_prefix); return TRUE; } /* ------------------------------------------------------------------------------------------------- */ static void do_mount (GVfsBackend *backend, GVfsJobMount *job, GMountSpec *mount_spec, GMountSource *mount_source, gboolean is_automount) { char *fuse_name; char *display_name; char *icon_name; const char *host; GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend); GError *error = NULL; GMountSpec *gphoto2_mount_spec; int rc; GPPortInfo info; GPPortInfoList *il = NULL; int n; CameraStorageInformation *storage_info; int num_storage_info; g_debug ("do_mount %p\n", gphoto2_backend); /* setup gudev */ const char *subsystems[] = {"usb", NULL}; gphoto2_backend->gudev_client = g_udev_client_new (subsystems); if (gphoto2_backend->gudev_client == NULL) { g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot create gudev client")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } g_signal_connect (gphoto2_backend->gudev_client, "uevent", G_CALLBACK (on_uevent), gphoto2_backend); /* setup gphoto2 */ host = g_mount_spec_get (mount_spec, "host"); g_debug (" host='%s'\n", host); if (host == NULL || strlen (host) < 3 || host[0] != '[' || host[strlen (host) - 1] != ']') { g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("No device specified")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } gphoto2_backend->gphoto2_port = g_strdup (host + 1); gphoto2_backend->gphoto2_port[strlen (gphoto2_backend->gphoto2_port) - 1] = '\0'; g_debug (" decoded host='%s'\n", gphoto2_backend->gphoto2_port); setup_for_device (gphoto2_backend); gphoto2_backend->context = gp_context_new (); if (gphoto2_backend->context == NULL) { g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot create gphoto2 context")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } rc = gp_camera_new (&(gphoto2_backend->camera)); if (rc != 0) { error = get_error_from_gphoto2 (_("Error creating camera"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } il = NULL; rc = gp_port_info_list_new (&il); if (rc != 0) { error = get_error_from_gphoto2 (_("Error loading device information"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } rc = gp_port_info_list_load (il); if (rc != 0) { error = get_error_from_gphoto2 (_("Error loading device information"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } g_debug (" gphoto2_port='%s'\n", gphoto2_backend->gphoto2_port); n = gp_port_info_list_lookup_path (il, gphoto2_backend->gphoto2_port); if (n == GP_ERROR_UNKNOWN_PORT) { error = get_error_from_gphoto2 (_("Error looking up device information"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } rc = gp_port_info_list_get_info (il, n, &info); if (rc != 0) { error = get_error_from_gphoto2 (_("Error getting device information"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } #ifndef HAVE_GPHOTO25 g_debug (" '%s' '%s' '%s'\n", info.name, info.path, info.library_filename); #endif /* set port */ rc = gp_camera_set_port_info (gphoto2_backend->camera, info); if (rc != 0) { error = get_error_from_gphoto2 (_("Error setting up camera communications port"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } gp_port_info_list_free(il); rc = gp_camera_init (gphoto2_backend->camera, gphoto2_backend->context); if (rc != 0) { error = get_error_from_gphoto2 (_("Error initializing camera"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return; } if (!ensure_ignore_prefix (gphoto2_backend, G_VFS_JOB (job))) return; /* Translator: %s represents the device, e.g. usb:001,042. 'gphoto2' is the name of the backend and shouldn't be translated. */ fuse_name = g_strdup_printf (_("gphoto2 mount on %s"), gphoto2_backend->gphoto2_port); display_name = compute_display_name (gphoto2_backend); g_vfs_backend_set_stable_name (backend, fuse_name); g_vfs_backend_set_display_name (backend, display_name); icon_name = compute_icon_name (gphoto2_backend); g_vfs_backend_set_icon_name (backend, icon_name); g_free (icon_name); icon_name = compute_symbolic_icon_name (gphoto2_backend); g_vfs_backend_set_symbolic_icon_name (backend, icon_name); g_free (icon_name); g_free (display_name); g_free (fuse_name); gphoto2_backend->can_write = FALSE; gphoto2_backend->can_delete = 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; gphoto2_backend->can_delete = TRUE; } if (storage_info[0].fields & GP_STORAGEINFO_ACCESS && storage_info[0].access == GP_STORAGEINFO_AC_READONLY_WITH_DELETE) { gphoto2_backend->can_delete = TRUE; } } } g_debug (" can_write = %d\n", gphoto2_backend->can_write); g_debug (" can_delete = %d\n", gphoto2_backend->can_delete); g_vfs_job_succeeded (G_VFS_JOB (job)); gphoto2_backend->free_space = -1; gphoto2_mount_spec = g_mount_spec_new ("gphoto2"); g_mount_spec_set (gphoto2_mount_spec, "host", host); g_vfs_backend_set_mount_spec (backend, gphoto2_mount_spec); g_mount_spec_unref (gphoto2_mount_spec); gphoto2_backend->info_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); gphoto2_backend->dir_name_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gp_list_unref); gphoto2_backend->file_name_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gp_list_unref); g_debug (" mounted %p\n", gphoto2_backend); } static void do_unmount (GVfsBackend *backend, GVfsJobUnmount *job, GMountUnmountFlags flags, GMountSource *mount_source) { GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend); g_signal_handlers_disconnect_by_func (gphoto2_backend->gudev_client, on_uevent, gphoto2_backend); g_vfs_job_succeeded (G_VFS_JOB (job)); } /* ------------------------------------------------------------------------------------------------- */ static gboolean try_mount (GVfsBackend *backend, GVfsJobMount *job, GMountSpec *mount_spec, GMountSource *mount_source, gboolean is_automount) { const char *host; GError *error = NULL; GMountSpec *gphoto2_mount_spec; g_debug ("try_mount %p\n", 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_debug (" host=%s\n", host); if (host == NULL) { g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, _("No camera specified")); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); return TRUE; } gphoto2_mount_spec = g_mount_spec_new ("gphoto2"); g_mount_spec_set (gphoto2_mount_spec, "host", host); g_vfs_backend_set_mount_spec (backend, gphoto2_mount_spec); g_mount_spec_unref (gphoto2_mount_spec); return FALSE; } /* ------------------------------------------------------------------------------------------------- */ static void free_read_handle (ReadHandle *read_handle) { if (read_handle->file != NULL) { gp_file_unref (read_handle->file); } g_free (read_handle); } static void do_open_for_read_real (GVfsBackend *backend, GVfsJobOpenForRead *job, const char *filename, gboolean get_preview) { int rc; GError *error; ReadHandle *read_handle; GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend); char *dir; char *name; ensure_not_dirty (gphoto2_backend); 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, _("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); if (rc != 0) { error = get_error_from_gphoto2 (_("Error creating file object"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); free_read_handle (read_handle); goto out; } rc = gp_camera_file_get (gphoto2_backend->camera, dir, name, get_preview ? GP_FILE_TYPE_PREVIEW : GP_FILE_TYPE_NORMAL, read_handle->file, gphoto2_backend->context); if (rc != 0) { error = get_error_from_gphoto2 (_("Error getting file"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); free_read_handle (read_handle); goto out; } rc = gp_file_get_data_and_size (read_handle->file, &read_handle->data, &read_handle->size); if (rc != 0) { error = get_error_from_gphoto2 (_("Error getting data from file"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); free_read_handle (read_handle); goto out; } g_debug (" data=%p size=%ld handle=%p get_preview=%d\n", read_handle->data, read_handle->size, read_handle, get_preview); g_mutex_lock (&gphoto2_backend->lock); gphoto2_backend->open_read_handles = g_list_prepend (gphoto2_backend->open_read_handles, read_handle); g_mutex_unlock (&gphoto2_backend->lock); read_handle->cursor = 0; g_vfs_job_open_for_read_set_can_seek (job, TRUE); g_vfs_job_open_for_read_set_handle (job, GINT_TO_POINTER (read_handle)); g_vfs_job_succeeded (G_VFS_JOB (job)); out: g_free (name); g_free (dir); } static void do_open_for_read (GVfsBackend *backend, GVfsJobOpenForRead *job, const char *filename) { g_debug ("open_for_read (%s)\n", filename); do_open_for_read_real (backend, job, filename, FALSE); } static void do_open_icon_for_read (GVfsBackend *backend, GVfsJobOpenIconForRead *job, const char *icon_id) { g_debug ("open_icon_for_read (%s)\n", icon_id); if (g_str_has_prefix (icon_id, "preview:")) { do_open_for_read_real (backend, G_VFS_JOB_OPEN_FOR_READ (job), icon_id + sizeof ("preview:") - 1, TRUE); } else { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Malformed icon identifier “%s”"), icon_id); } } /* ------------------------------------------------------------------------------------------------- */ 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_debug ("do_read() %" G_GSIZE_FORMAT " @ %ld of %" G_GSIZE_FORMAT ", handle=%p\n", 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; else bytes_to_copy = bytes_requested; memcpy (buffer, read_handle->data + read_handle->cursor, bytes_to_copy); read_handle->cursor += bytes_to_copy; out: g_vfs_job_read_set_size (job, bytes_to_copy); g_vfs_job_succeeded (G_VFS_JOB (job)); return TRUE; } /* ------------------------------------------------------------------------------------------------- */ 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_debug ("seek_on_read() offset=%d, type=%d, handle=%p\n", (int)offset, type, handle); switch (type) { default: case G_SEEK_SET: new_offset = offset; break; case G_SEEK_CUR: new_offset = read_handle->cursor + offset; break; case G_SEEK_END: new_offset = read_handle->size + offset; break; } if (new_offset < 0) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error seeking in stream on camera %s"), gphoto2_backend->gphoto2_port); } else { read_handle->cursor = new_offset; g_vfs_job_seek_read_set_offset (job, new_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) { ReadHandle *read_handle = (ReadHandle *) handle; GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend); g_debug ("close_read() handle=%p\n", handle); g_mutex_lock (&gphoto2_backend->lock); gphoto2_backend->open_read_handles = g_list_remove (gphoto2_backend->open_read_handles, read_handle); g_mutex_unlock (&gphoto2_backend->lock); free_read_handle (read_handle); g_vfs_job_succeeded (G_VFS_JOB (job)); } /* ------------------------------------------------------------------------------------------------- */ static void do_query_info (GVfsBackend *backend, GVfsJobQueryInfo *job, const char *filename, GFileQueryInfoFlags flags, GFileInfo *info, GFileAttributeMatcher *matcher) { GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend); GError *error; char *dir; char *name; g_debug ("query_info (%s)\n", filename); split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name); error = NULL; if (!file_get_info (gphoto2_backend, dir, name, info, &error, FALSE)) { g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); } else { g_vfs_job_succeeded (G_VFS_JOB (job)); } g_free (name); g_free (dir); } /* ------------------------------------------------------------------------------------------------- */ 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); char *dir; char *name; gboolean ret; g_debug ("try_query_info (%s)\n", filename); ret = FALSE; split_filename_with_ignore_prefix (gphoto2_backend, filename, &dir, &name); if (!file_get_info (gphoto2_backend, dir, name, info, NULL, TRUE)) { g_debug (" BUU no info from cache for try_query_info (%s)\n", filename); goto out; } g_debug (" YAY got info from cache for try_query_info (%s)\n", 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, const char *given_filename, GFileAttributeMatcher *matcher, GFileQueryInfoFlags flags) { GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend); GFileInfo *info; GList *l; GError *error; CameraList *list; int n; int rc; char *filename; gboolean using_cached_dir_list; gboolean using_cached_file_list; char *as_dir; char *as_name; l = NULL; using_cached_dir_list = FALSE; using_cached_file_list = FALSE; filename = add_ignore_prefix (gphoto2_backend, given_filename); g_debug ("enumerate ('%s', with_prefix='%s')\n", given_filename, 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, _("Not a directory")); } else { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("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); g_debug (" Generating dir list for dir '%s'\n", filename); gp_list_new (&list); rc = gp_camera_folder_list_folders (gphoto2_backend->camera, filename, list, gphoto2_backend->context); if (rc != 0) { error = get_error_from_gphoto2 (_("Failed to get folder list"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); g_free (filename); return; } } else { g_debug (" Using cached dir list for dir '%s'\n", 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; gp_list_get_name (list, n, &name); g_debug (" enum folder '%s'\n", name); info = g_file_info_new (); error = NULL; if (!file_get_info (gphoto2_backend, filename, name, info, &error, FALSE)) { g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); g_list_free_full (l, g_object_unref); gp_list_free (list); return; } l = g_list_append (l, info); } if (!using_cached_dir_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); } /* 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); g_debug (" Generating file list for dir '%s'\n", filename); gp_list_new (&list); rc = gp_camera_folder_list_files (gphoto2_backend->camera, filename, list, gphoto2_backend->context); if (rc != 0) { error = get_error_from_gphoto2 (_("Failed to get file list"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); g_free (filename); return; } } else { g_debug (" Using cached file list for dir '%s'\n", 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); g_debug (" enum file '%s'\n", name); info = g_file_info_new (); error = NULL; if (!file_get_info (gphoto2_backend, filename, name, info, &error, FALSE)) { g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); g_list_free_full (l, g_object_unref); gp_list_free (list); return; } l = g_list_append (l, info); } if (!using_cached_file_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_free_full (l, g_object_unref); 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); g_debug ("try_enumerate (%s)\n", 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); g_debug (" try_enum folder '%s'\n", 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); g_debug (" try_enum file '%s'\n", 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 */ g_vfs_job_succeeded (G_VFS_JOB (job)); g_vfs_job_enumerate_add_infos (job, l); g_list_free_full (l, g_object_unref); g_vfs_job_enumerate_done (job); g_free (filename); g_debug (" YAY got info from cache for try_enumerate (%s)\n", given_filename); return TRUE; error_not_cached: g_list_free_full (l, g_object_unref); g_free (filename); g_debug (" BUU no info from cache for try_enumerate (%s)\n", given_filename); return FALSE; } /* ------------------------------------------------------------------------------------------------- */ static void do_query_fs_info (GVfsBackend *backend, GVfsJobQueryFsInfo *job, const char *filename, GFileInfo *info, GFileAttributeMatcher *attribute_matcher) { int rc; GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend); CameraStorageInformation *storage_info; int num_storage_info; g_debug ("query_fs_info (%s)\n", 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_REMOTE, FALSE); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_NEVER); 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) { /* 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, (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, (guint64) gphoto2_backend->free_space); } } g_debug (" got %d storage_info objects\n", 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; g_debug ("try_query_fs_info (%s)\n", 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) { g_debug (" BUU no info from cache for try_query_fs_info (%s)\n", filename); goto out; } g_debug (" YAY got info from cache for try_query_fs_info (%s)\n", 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_NEVER); 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; g_debug ("make_directory (%s)\n", 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, _("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 (_("Error creating directory"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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; g_debug ("do_slow_file_rename_in_same_dir() '%s' '%s' -> '%s'\n", 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) { g_debug (" file delete failed as part of slow rename rc=%d\n", rc); goto out; } } #ifdef HAVE_GPHOTO25 rc = gp_camera_folder_put_file (gphoto2_backend->camera, dir, new_name, GP_FILE_TYPE_NORMAL, file_dest, gphoto2_backend->context); #else rc = gp_camera_folder_put_file (gphoto2_backend->camera, dir, file_dest, gphoto2_backend->context); #endif 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); g_debug ("do_dir_rename_in_same_dir() '%s' '%s' -> '%s' ('%s')\n", 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); g_debug ("set_display_name() '%s' -> '%s'\n", 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, _("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, _("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, _("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 (_("Error renaming directory"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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 (_("Error renaming file"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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); g_debug ("delete() '%s'\n", filename); dir = NULL; name = NULL; dir_name = NULL; if (!gphoto2_backend->can_delete) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("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, _("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 (_("Error deleting directory"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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, _("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 (_("Error deleting file"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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, _("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, _("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, _("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 (_("Cannot allocate new file to append to"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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 (_("Cannot read file to append to"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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 (_("Cannot get data of file to append to"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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); g_vfs_job_open_for_write_set_can_truncate (job, TRUE); gphoto2_backend->open_write_handles = g_list_prepend (gphoto2_backend->open_write_handles, handle); g_debug (" handle=%p\n", 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) { g_debug ("create() '%s' flags=0x%04x\n", 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; g_debug ("replace() '%s' etag='%s' make_backup=%d flags=0x%04x\n", 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; g_debug ("append_to() '%s' flags=0x%04x\n", 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; g_debug ("write() %p, '%s', %" G_GSIZE_FORMAT " bytes\n", 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; g_debug (" allocated_size is now %ld bytes)\n", 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; g_debug ("seek_on_write() %p '%s' offset=%d type=%d cursor=%ld size=%ld\n", 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) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error seeking in stream on camera %s"), gphoto2_backend->gphoto2_port); } else { write_handle->cursor = new_offset; g_vfs_job_seek_write_set_offset (job, new_offset); g_vfs_job_succeeded (G_VFS_JOB (job)); } } /* ------------------------------------------------------------------------------------------------- */ static void do_truncate (GVfsBackend *backend, GVfsJobTruncate *job, GVfsBackendHandle _handle, goffset size) { WriteHandle *handle = _handle; g_debug ("truncate() %p, '%s', %ld bytes\n", handle, handle->filename, size); /* ensure we have enough room */ if (size > handle->allocated_size) { unsigned long int new_allocated_size; new_allocated_size = (size / WRITE_INCREMENT + 1) * WRITE_INCREMENT; handle->data = g_realloc (handle->data, new_allocated_size); handle->allocated_size = new_allocated_size; g_debug (" allocated_size is now %ld bytes)\n", handle->allocated_size); } if (size > handle->size) memset(handle->data + handle->size, 0, size - handle->size); handle->size = size; /* this will make us dirty */ handle->is_dirty = TRUE; 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; g_debug ("commit_write_handle() '%s' of size %ld\n", write_handle->filename, write_handle->size); /* no need to write as we're not dirty */ if (!write_handle->is_dirty) { g_debug (" not dirty => not writing\n"); 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; g_debug (" deleted '%s' '%s' for delete_before=%d, job_is_replace=%d, job_is_append_to=%d\n", 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_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); #ifdef HAVE_GPHOTO25 rc = gp_camera_folder_put_file (gphoto2_backend->camera, write_handle->dir, write_handle->name, GP_FILE_TYPE_NORMAL, file, gphoto2_backend->context); #else gp_file_set_type (file, GP_FILE_TYPE_NORMAL); rc = gp_camera_folder_put_file (gphoto2_backend->camera, write_handle->dir, file, gphoto2_backend->context); #endif if (rc != 0) { gp_file_unref (file); goto out; } g_debug (" successfully wrote '%s' of %ld bytes\n", 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; g_debug ("close_write() %p '%s' %ld bytes total\n", write_handle, write_handle->filename, write_handle->size); rc = commit_write_handle (gphoto2_backend, write_handle); if (rc != 0) { error = get_error_from_gphoto2 (_("Error writing file"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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; g_debug ("move() '%s' -> '%s' %04x)\n", 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) { g_debug (" not supported (not same directory)\n"); g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("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)) { g_debug (" not supported (src is dir; dst is dir)\n"); g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Not supported (the source is a directory, the destination is a directory too)")); goto out; } else if (is_regular (gphoto2_backend, dst_dir, dst_name)) { g_debug (" not supported (src is dir; dst is existing file)\n"); g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Not supported (the source is a directory, but the destination is an existing file)")); goto out; } mv_dir = TRUE; } else { if (is_directory (gphoto2_backend, dst_dir, dst_name)) { g_debug (" not supported (src is file; dst is dir)\n"); g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Not supported (the source is a file, but the destination is a directory)")); 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, _("New name too long")); goto out; } if (mv_dir) { g_debug (" renaming dir\n"); rc = do_dir_rename_in_same_dir (gphoto2_backend, src_dir, src_name, dst_name); if (rc != 0) { g_debug (" error renaming dir\n"); error = get_error_from_gphoto2 (_("Error renaming directory"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (error); goto out; } } else { g_debug (" renaming file\n"); rc = do_file_rename_in_same_dir (gphoto2_backend, src_dir, src_name, dst_name, flags & G_FILE_COPY_OVERWRITE); if (rc != 0) { g_debug (" error renaming file\n"); error = get_error_from_gphoto2 (_("Error renaming file"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_error_free (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); g_debug (" success\n"); 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 unsigned int ctx_progress_start_func (GPContext *context, float target, const char *str, void *data) { PullContext *pc = data; pc->target = target; return 0; } static void ctx_progress_update_func (GPContext *context, unsigned int id, float current, void *data) { PullContext *pc = data; if (pc->progress_callback) pc->progress_callback ((current / pc->target) * pc->size, pc->size, pc->progress_callback_data); } static void ctx_progress_stop_func (GPContext *context, unsigned int id, void *data) { PullContext *pc = data; if (pc->progress_callback) pc->progress_callback (pc->size, pc->size, pc->progress_callback_data); } static void do_pull (GVfsBackend *backend, GVfsJobPull *job, const char *source, const char *local_path, GFileCopyFlags flags, gboolean remove_source, GFileProgressCallback progress_callback, gpointer progress_callback_data) { GVfsBackendGphoto2 *gphoto2_backend = G_VFS_BACKEND_GPHOTO2 (backend); GFileInfo *info = g_file_info_new (); GError *error = NULL; PullContext pc; CameraFile *file; GFile *dest = NULL; GFileDescriptorBased *fdstream; char *dir, *name; guint64 mtime; int rc; ensure_not_dirty (gphoto2_backend); split_filename_with_ignore_prefix (gphoto2_backend, source, &dir, &name); if (remove_source && !gphoto2_backend->can_delete) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Not supported")); goto out; } /* Fallback to the default implementation unless we have a regular file */ if (!file_get_info (gphoto2_backend, dir, name, info, &error, FALSE) || g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR) { g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Not supported")); goto out; } dest = g_file_new_for_path (local_path); if (flags & G_FILE_COPY_OVERWRITE) { fdstream = G_FILE_DESCRIPTOR_BASED ( g_file_replace (dest, NULL, flags & G_FILE_COPY_BACKUP ? TRUE : FALSE, G_FILE_CREATE_REPLACE_DESTINATION, G_VFS_JOB (job)->cancellable, &error)); } else { fdstream = G_FILE_DESCRIPTOR_BASED ( g_file_create (dest, G_FILE_CREATE_NONE, G_VFS_JOB (job)->cancellable, &error)); } if (!fdstream) { g_vfs_job_failed_from_error (G_VFS_JOB (job), error); goto out; } rc = gp_file_new_from_fd (&file, g_file_descriptor_based_get_fd (fdstream)); if (rc != 0) { error = get_error_from_gphoto2 (_("Error creating file object"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); g_object_unref (fdstream); goto out; } pc.size = g_file_info_get_size (info); pc.progress_callback = progress_callback; pc.progress_callback_data = progress_callback_data; gp_context_set_progress_funcs (gphoto2_backend->context, ctx_progress_start_func, ctx_progress_update_func, ctx_progress_stop_func, &pc); rc = gp_camera_file_get (gphoto2_backend->camera, dir, name, GP_FILE_TYPE_NORMAL, file, gphoto2_backend->context); gp_context_set_progress_funcs (gphoto2_backend->context, NULL, NULL, NULL, NULL); /* gp_camera_file_get() closes the fd so we just unref here */ g_object_unref (fdstream); gp_file_unref (file); if (rc != 0) { error = get_error_from_gphoto2 (_("Error getting file"), rc); g_vfs_job_failed_from_error (G_VFS_JOB (job), error); goto out; } /* Ignore errors here. Failure to copy metadata is not a hard error */ mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED); g_file_set_attribute_uint64 (dest, G_FILE_ATTRIBUTE_TIME_MODIFIED, mtime, G_FILE_QUERY_INFO_NONE, G_VFS_JOB (job)->cancellable, NULL); if (remove_source) { rc = gp_camera_file_delete (gphoto2_backend->camera, dir, name, gphoto2_backend->context); if (rc != 0) { error = get_error_from_gphoto2 (_("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_object_unref (info); g_clear_object (&dest); g_free (name); g_free (dir); g_clear_error (&error); } /* ------------------------------------------------------------------------------------------------- */ 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); g_debug ("vfs_dir_monitor_destroyed()\n"); 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); g_debug (" Removed dead dir monitor for '%s'\n", 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; g_debug ("create_dir_monitor (%s)\n", 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); g_debug ("vfs_file_monitor_destroyed()\n"); 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); g_debug (" Removed dead file monitor for '%s'\n", 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; g_debug ("create_file_monitor (%s)\n", 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) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); gobject_class->finalize = g_vfs_backend_gphoto2_finalize; backend_class->try_mount = try_mount; backend_class->mount = do_mount; backend_class->unmount = do_unmount; backend_class->open_icon_for_read = do_open_icon_for_read; backend_class->open_for_read = do_open_for_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->truncate = do_truncate; backend_class->move = do_move; backend_class->pull = do_pull; 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; }