/* GIO - GLib Input, Output and Streaming Library
 *   MTP Backend
 *
 * Copyright (C) 2012 Philip Langdale <philipl@overt.org>
 *
 * 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, see
 * <http://www.gnu.org/licenses/>.
 */


#include <config.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>

#include <glib/gstdio.h>
#include <glib/gi18n.h>
#include <gio/gio.h>

#include <libmtp.h>

#include "gvfsbackendmtp.h"
#include "gvfsicon.h"
#include "gvfsjobopenforread.h"
#include "gvfsjobread.h"
#include "gvfsjobseekread.h"
#include "gvfsjobopenforwrite.h"
#include "gvfsjobwrite.h"
#include "gvfsjobclosewrite.h"
#include "gvfsjobseekwrite.h"
#include "gvfsjobsetdisplayname.h"
#include "gvfsjobqueryinfo.h"
#include "gvfsjobdelete.h"
#include "gvfsjobqueryfsinfo.h"
#include "gvfsjobqueryattributes.h"
#include "gvfsjobenumerate.h"
#include "gvfsdaemonprotocol.h"
#include "gvfsjobcreatemonitor.h"
#include "gvfsjobmakedirectory.h"
#include "gvfsmonitor.h"
#include "gvfsgphoto2utils.h"


/* ------------------------------------------------------------------------------------------------- */

/* showing debug traces */
#define DEBUG_SHOW_TRACES 1
#define DEBUG_SHOW_ENUMERATE_TRACES 0

static void
DEBUG (const gchar *message, ...)
{
#if DEBUG_SHOW_TRACES
  va_list args;
  va_start (args, message);
  g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, message, args);
  va_end (args);
#endif
}

static void
DEBUG_ENUMERATE (const gchar *message, ...)
{
#if DEBUG_SHOW_ENUMERATE_TRACES
  va_list args;
  va_start (args, message);
  g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_DEBUG, message, args);
  va_end (args);
#endif
}


/************************************************
 * Storage constants copied from ptp.h
 *
 * ptp.h is treated as a private header by libmtp
 ************************************************/

/* PTP Storage Types */

#define PTP_ST_Undefined                        0x0000
#define PTP_ST_FixedROM                         0x0001
#define PTP_ST_RemovableROM                     0x0002
#define PTP_ST_FixedRAM                         0x0003
#define PTP_ST_RemovableRAM                     0x0004


/************************************************
 * Private Types
 ************************************************/

typedef enum {
  HANDLE_FILE,
  HANDLE_PREVIEW,
} HandleType;

typedef struct {
  HandleType handle_type;
  uint32_t id;
  goffset offset;
  gsize size;

  /* For previews only */
  GByteArray *bytes;
} RWHandle;

typedef struct {
  uint32_t storage;
  uint32_t id;
} CacheEntry;


/************************************************
 * Static prototypes
 ************************************************/

static void
emit_delete_event (gpointer key,
                   gpointer value,
                   gpointer user_data);


/************************************************
 * Storage name helper
 ************************************************/

static char *create_storage_name (const LIBMTP_devicestorage_t *storage)
{
  /* The optional post-fixing of storage's name with ID requires us to
     know in advance whether the storage's description string is unique
     or not. Since this function is called in several places, it is
     safest to perform this check here, each time that storage name needs
     to be created. */
  gboolean is_unique = TRUE;
  const LIBMTP_devicestorage_t *tmp_storage;

  /* Forward search for duplicates */
  for (tmp_storage = storage->next; tmp_storage != 0; tmp_storage = tmp_storage->next) {
    if (!g_strcmp0 (storage->StorageDescription, tmp_storage->StorageDescription)) {
      is_unique = FALSE;
      break;
    }
  }

  /* Backward search, if necessary */
  if (is_unique) {
    for (tmp_storage = storage->prev; tmp_storage != 0; tmp_storage = tmp_storage->prev) {
      /* Compare descriptions */
      if (!g_strcmp0 (storage->StorageDescription, tmp_storage->StorageDescription)) {
        is_unique = FALSE;
        break;
      }
    }
  }

  /* If description is unique, we can use it as storage name; otherwise,
     we add storage ID to it */
  if (is_unique) {
    return g_strdup (storage->StorageDescription);
  } else {
    return g_strdup_printf ("%s (%X)", storage->StorageDescription, storage->id);
  }
}


/************************************************
 * Cache Helpers
 ************************************************/

static void
add_cache_entry (GVfsBackendMtp *backend,
                 char *path,
                 uint32_t storage,
                 uint32_t id)
{
  CacheEntry *entry = g_new0 (CacheEntry, 1);
  entry->storage = storage;
  entry->id = id;
  DEBUG ("(II) add_cache_entry: %s: %X, %X\n",
         path, entry->storage, entry->id);
  g_hash_table_replace (backend->file_cache,
                        path, entry);
}


static char *
build_partial_path (char **elements,
                    unsigned int ne)
{
  char **pe = g_new0 (char *, ne + 2);
  int i;
  pe[0] = g_strdup("/");
  for (i = 0; i < ne; i++) {
    pe[i + 1] = elements[i];
  }
  char *path = g_build_filenamev(pe);
  g_free (pe);
  return path;
}

/**
 * get_file_for_filename:
 *
 * Get the entity ID for an element given its filename and
 * the IDs of its parents.
 *
 * Called with backend mutex lock held.
 */
static void
add_cache_entries_for_filename (GVfsBackendMtp *backend,
                                const char *path)
{
  LIBMTP_file_t *file = NULL;
  LIBMTP_mtpdevice_t *device = backend->device;

  gchar **elements = g_strsplit_set (path, "/", -1);
  unsigned int ne = g_strv_length (elements);

  DEBUG ("(III) add_cache_entries_for_filename: %s, %u\n", path, ne);

  if (ne < 2) {
    DEBUG ("(III) Ignoring query on invalid path\n");
    goto exit;
  }

  /* Identify Storage */
  LIBMTP_devicestorage_t *storage;

  int ret = LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED);
  if (ret != 0) {
    LIBMTP_Dump_Errorstack (device);
    LIBMTP_Clear_Errorstack (device);
    goto exit;
  }
  for (storage = device->storage; storage != 0; storage = storage->next) {
    /* Construct the name for storage and compare it to first element of path */
    char *storage_name = create_storage_name (storage);
    int is_equal = !g_strcmp0 (elements[1], storage_name);
    g_free(storage_name);

    if (is_equal) {
      char *partial = build_partial_path (elements, 2);
      add_cache_entry (backend, partial, storage->id, -1);
      break;
    }
  }
  if (!storage) {
    DEBUG ("(III) Ignoring query on invalid storage\n");
    goto exit;
  }

  long parent_id = -1;
  int i;

  for (i = 2; i < ne; i++) {
    LIBMTP_file_t *f =
      LIBMTP_Get_Files_And_Folders (device, storage->id, parent_id);
    while (f != NULL) {
      DEBUG_ENUMERATE ("(III) query (entity = %s, name = %s)\n", f->filename, elements[i]);
      if (strcmp (f->filename, elements[i]) == 0) {
        file = f;
        f = f->next;
        char *partial = build_partial_path (elements, i + 1);
        add_cache_entry (backend, partial, file->storage_id, file->item_id);
        break;
      } else {
        LIBMTP_file_t *tmp = f;
        f = f->next;
        LIBMTP_destroy_file_t (tmp);
      }
    }
    while (f != NULL) {
      LIBMTP_file_t *tmp = f;
      f = f->next;
      LIBMTP_destroy_file_t (tmp);
    }
    if (!file) {
      DEBUG ("(III) Ignoring query for non-existent file\n");
      goto exit;
    }
    parent_id = file->item_id;
  }

 exit:
  g_strfreev (elements);

  DEBUG ("(III) add_cache_entries_for_filename done\n");
}


static CacheEntry *get_cache_entry (GVfsBackendMtp *backend,
                                    const char *path)
{
  DEBUG ("(III) get_cache_entry: %s\n", path);
  CacheEntry *entry = g_hash_table_lookup (backend->file_cache, path);
  if (!entry) {
    add_cache_entries_for_filename (backend, path);
    entry = g_hash_table_lookup (backend->file_cache, path);
  }
  DEBUG ("(III) get_cache_entry done: %p\n", entry);
  return entry;
}


static gboolean
remove_cache_entry_by_prefix (gpointer key,
                              gpointer value,
                              gpointer user_data)
{
  const char *path = key;
  const char *prefix = user_data;

  return g_str_has_prefix (path, prefix);
}


static void
remove_cache_entry (GVfsBackendMtp *backend,
                    const char *path)
{
  DEBUG ("(III) remove_cache_entry: %s\n", path);
  g_hash_table_foreach_remove (backend->file_cache,
                               remove_cache_entry_by_prefix,
                               (gpointer) path);
  DEBUG ("(III) remove_cache_entry done\n");
}


static void
remove_cache_entry_by_id (GVfsBackendMtp *backend,
                          uint32_t id)
{
  GHashTableIter iter;
  gpointer key, value;
  DEBUG ("(III) remove_cache_entry_by_id: %X\n", id);

  g_hash_table_iter_init (&iter, backend->file_cache);
  while (g_hash_table_iter_next (&iter, &key, &value)) {
    const char *path = key;
    const CacheEntry *entry = value;

    if (entry->id == id || (entry->id == -1 && entry->storage == id)) {
      g_hash_table_foreach (backend->monitors,
                            emit_delete_event,
                            (char *)path);
      g_hash_table_iter_remove (&iter);

      /* We do not break here because we can end up with multiple entries
         that have same storage/object ID, and we should find and remove
         them all instead of just the first one (Bug #733886) */
    }
  }

  DEBUG ("(III) remove_cache_entry_by_id done\n");
}


/************************************************
 * Initialization
 ************************************************/

G_DEFINE_TYPE (GVfsBackendMtp, g_vfs_backend_mtp, G_VFS_TYPE_BACKEND)

static void
g_vfs_backend_mtp_init (GVfsBackendMtp *backend)
{
  DEBUG ("(I) g_vfs_backend_mtp_init\n");
  GMountSpec *mount_spec;

  g_mutex_init (&backend->mutex);
  g_vfs_backend_set_display_name (G_VFS_BACKEND (backend), "mtp");
  g_vfs_backend_set_icon_name (G_VFS_BACKEND (backend), "multimedia-player");

  mount_spec = g_mount_spec_new ("mtp");
  g_vfs_backend_set_mount_spec (G_VFS_BACKEND (backend), mount_spec);
  g_mount_spec_unref (mount_spec);

  backend->monitors = g_hash_table_new (NULL, NULL);

  DEBUG ("(I) g_vfs_backend_mtp_init done.\n");
}

static void
remove_monitor_weak_ref (gpointer monitor,
                         gpointer unused,
                         gpointer monitors)
{
  g_object_weak_unref (G_OBJECT(monitor), (GWeakNotify)g_hash_table_remove, monitors);
}

static void
g_vfs_backend_mtp_finalize (GObject *object)
{
  GVfsBackendMtp *backend;

  DEBUG ("(I) g_vfs_backend_mtp_finalize\n");

  backend = G_VFS_BACKEND_MTP (object);

  g_hash_table_foreach (backend->monitors, remove_monitor_weak_ref, backend->monitors);
  g_hash_table_unref (backend->monitors);

  /* Leak the mutex if the backend is force unmounted to avoid crash caused by
   * abort(), when trying to clear already locked mutex. */
  if (!backend->force_unmounted)
    g_mutex_clear (&backend->mutex);

  (*G_OBJECT_CLASS (g_vfs_backend_mtp_parent_class)->finalize) (object);

  DEBUG ("(I) g_vfs_backend_mtp_finalize done.\n");
}


/************************************************
 * Monitors
 ************************************************/

/**
 * do_create_dir_monitor:
 *
 * Called with backend mutex lock held.
 */
static void
do_create_dir_monitor (GVfsBackend *backend,
                       GVfsJobCreateMonitor *job,
                       const char *filename,
                       GFileMonitorFlags flags)
{
  GVfsBackendMtp *mtp_backend = G_VFS_BACKEND_MTP (backend);

  DEBUG ("(I) create_dir_monitor (%s)\n", filename);

  GVfsMonitor *vfs_monitor = g_vfs_monitor_new (backend);

  g_object_set_data_full (G_OBJECT (vfs_monitor), "gvfsbackendmtp:path",
                          g_strdup (filename), g_free);

  g_vfs_job_create_monitor_set_monitor (job, vfs_monitor);
  g_hash_table_add (mtp_backend->monitors, vfs_monitor);
  g_object_weak_ref (G_OBJECT (vfs_monitor), (GWeakNotify)g_hash_table_remove, mtp_backend->monitors);
  g_object_unref (vfs_monitor);
  g_vfs_job_succeeded (G_VFS_JOB (job));

  DEBUG ("(I) create_dir_monitor done.\n");
}


/**
 * do_create_file_monitor:
 *
 * Called with backend mutex lock held.
 */
static void
do_create_file_monitor (GVfsBackend *backend,
                        GVfsJobCreateMonitor *job,
                        const char *filename,
                        GFileMonitorFlags flags)
{
  GVfsBackendMtp *mtp_backend = G_VFS_BACKEND_MTP (backend);

  DEBUG ("(I) create_file_monitor (%s)\n", filename);

  GVfsMonitor *vfs_monitor = g_vfs_monitor_new (backend);

  g_object_set_data_full (G_OBJECT (vfs_monitor), "gvfsbackendmtp:path",
                          g_strdup (filename), g_free);

  g_vfs_job_create_monitor_set_monitor (job, vfs_monitor);
  g_hash_table_add (mtp_backend->monitors, vfs_monitor);
  g_object_weak_ref (G_OBJECT (vfs_monitor), (GWeakNotify)g_hash_table_remove, mtp_backend->monitors);
  g_object_unref (vfs_monitor);
  g_vfs_job_succeeded (G_VFS_JOB (job));

  DEBUG ("(I) create_file_monitor done.\n");
}


static void
emit_event_internal (GVfsMonitor *monitor,
                     const char *path,
                     GFileMonitorEvent event)
{
  DEBUG ("(III) emit_event_internal (%s, %d)\n", path, event);

  char *dir = g_dirname (path);
  const char *monitored_path = g_object_get_data (G_OBJECT (monitor), "gvfsbackendmtp:path");
  if (g_strcmp0 (dir, monitored_path) == 0) {
    DEBUG ("(III) emit_event_internal: Event %d on directory %s for %s\n", event, dir, path);
    g_vfs_monitor_emit_event (monitor, event, path, NULL);
  } else if (g_strcmp0 (path, monitored_path) == 0) {
    DEBUG ("(III) emit_event_internal: Event %d on file %s\n", event, path);
    g_vfs_monitor_emit_event (monitor, event, path, NULL);
  }
  g_free (dir);

  DEBUG ("(III) emit_event_internal done.\n");
}


static void
emit_create_event (gpointer key,
                   gpointer value,
                   gpointer user_data)
{
  DEBUG ("(II) emit_create_event.\n");
  emit_event_internal (key, user_data, G_FILE_MONITOR_EVENT_CREATED);
}


static void
emit_delete_event (gpointer key,
                   gpointer value,
                   gpointer user_data)
{
  DEBUG ("(II) emit_delete_event.\n");
  emit_event_internal (key, user_data, G_FILE_MONITOR_EVENT_DELETED);
}


/************************************************
 * Errors
 ************************************************/

static void
fail_job (GVfsJob *job, LIBMTP_mtpdevice_t *device)
{
  const char *text;
  LIBMTP_error_t *error = LIBMTP_Get_Errorstack (device);

  if (error) {
    text = g_strrstr (error->error_text, ":") + 1;
  } else {
    text = _("Unknown error.");
  }
  g_vfs_job_failed (job, G_IO_ERROR,
                    g_vfs_job_is_cancelled (job) ?
                      G_IO_ERROR_CANCELLED :
                      G_IO_ERROR_FAILED,
                    _("libmtp error: %s"),
                    text);

  LIBMTP_Clear_Errorstack (device);
}


/************************************************
 * Mounts
 ************************************************/

static LIBMTP_mtpdevice_t *
get_device (GVfsBackend *backend, const char *id, GVfsJob *job);


static void
on_uevent (GUdevClient *client, gchar *action, GUdevDevice *device, gpointer user_data)
{
  const char *dev_path = g_udev_device_get_device_file (device);
  DEBUG ("(I) on_uevent (action %s, device %s)\n", action, dev_path);

  if (dev_path == NULL) {
    return;
  }

  GVfsBackendMtp *op_backend = G_VFS_BACKEND_MTP (user_data);

  if (g_strcmp0 (op_backend->dev_path, dev_path) == 0 &&
      g_str_equal (action, "remove")) {
    DEBUG ("(I) on_uevent: Quiting after remove event on device %s\n", dev_path);

    /* Emit delete events to tell clients files are gone. */
    GHashTableIter iter;
    gpointer key, value;
    g_hash_table_iter_init (&iter, op_backend->file_cache);
    while (g_hash_table_iter_next (&iter, &key, &value)) {
      const char *path = key;

      g_hash_table_foreach (op_backend->monitors,
                            emit_delete_event,
                           (char *)path);
    }

    op_backend->force_unmounted = TRUE;
    g_vfs_backend_force_unmount ((GVfsBackend*)op_backend);
  }

  DEBUG ("(I) on_uevent done.\n");
}

#if HAVE_LIBMTP_1_1_5
static gpointer
check_event (gpointer user_data)
{
  GWeakRef *event_ref = user_data;

  LIBMTP_event_t event;
  int ret = 0;
  while (ret == 0) {
    uint32_t param1;
    GVfsBackendMtp *backend;

    backend = g_weak_ref_get (event_ref);
    if (backend && !g_atomic_int_get (&backend->unmount_started)) {
      LIBMTP_mtpdevice_t *device = backend->device;
      g_object_unref (backend);
      /*
       * Unavoidable race. We can't hold a reference when
       * calling Read_Event as it blocks while waiting and
       * we can't interrupt it in any sane way, so it would
       * end up preventing finalization of the backend.
       */
      ret = LIBMTP_Read_Event (device, &event, &param1);
    } else {
      return NULL;
    }

    switch (event) {
    case LIBMTP_EVENT_STORE_ADDED:
      backend = g_weak_ref_get (event_ref);
      if (backend && !g_atomic_int_get (&backend->unmount_started)) {
        LIBMTP_mtpdevice_t *device = backend->device;
        LIBMTP_devicestorage_t *storage;

        g_mutex_lock (&backend->mutex);

        int ret = LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED);
        if (ret != 0) {
          LIBMTP_Dump_Errorstack (device);
          LIBMTP_Clear_Errorstack (device);
        } else {
          for (storage = device->storage; storage != 0; storage = storage->next) {
            if (storage->id == param1) {
              char *storage_name = create_storage_name (storage);
              char *path = g_build_filename ("/", storage_name, NULL);
              g_free (storage_name);

              add_cache_entry (G_VFS_BACKEND_MTP (backend),
                              path,
                              storage->id,
                              -1);
              g_hash_table_foreach (backend->monitors, emit_create_event, path);
            }
          }
        }

        g_mutex_unlock (&backend->mutex);
        g_object_unref (backend);
        break;
      } else {
        return NULL;
      }
#if HAVE_LIBMTP_1_1_6
    case LIBMTP_EVENT_OBJECT_REMOVED:
      backend = g_weak_ref_get (event_ref);
      if (backend && !g_atomic_int_get (&backend->unmount_started)) {
        g_mutex_lock (&backend->mutex);
        remove_cache_entry_by_id (G_VFS_BACKEND_MTP (backend), param1);
        g_mutex_unlock (&backend->mutex);
        g_object_unref (backend);
        break;
      } else {
        return NULL;
      }
    case LIBMTP_EVENT_STORE_REMOVED:
      backend = g_weak_ref_get (event_ref);
      if (backend && !g_atomic_int_get (&backend->unmount_started)) {
        g_mutex_lock (&backend->mutex);

        /* Clear the cache entries and emit delete event; first for all
           entries under the storage in question... */
        GHashTableIter iter;
        gpointer key, value;
        g_hash_table_iter_init (&iter, backend->file_cache);
        while (g_hash_table_iter_next (&iter, &key, &value)) {
          const char *path = key;
          const CacheEntry *entry = value;

          if (entry->storage == param1) {
            g_hash_table_foreach (backend->monitors,
                                  emit_delete_event,
                                  (char *)path);
            g_hash_table_iter_remove (&iter);
          }
        }

        /* ... and then for the storage itself */
        remove_cache_entry_by_id (G_VFS_BACKEND_MTP (backend), param1);

        g_mutex_unlock (&backend->mutex);
        g_object_unref (backend);
        break;
      } else {
        return NULL;
      }
    case LIBMTP_EVENT_OBJECT_ADDED:
      backend = g_weak_ref_get (event_ref);
      if (backend && !g_atomic_int_get (&backend->unmount_started)) {
        g_mutex_lock (&backend->mutex);

        LIBMTP_file_t *object = LIBMTP_Get_Filemetadata(backend->device, param1);
        if (object) {
          /* Obtain parent's path by searching cache by object's parent
             ID; if the latter is zero, the object is located in storage's
             root, otherwise, it is located in one of subfolders */
          const char *parent_path = NULL;
          GHashTableIter iter;
          gpointer key, value;

          g_hash_table_iter_init (&iter, backend->file_cache);
          while (g_hash_table_iter_next (&iter, &key, &value)) {
            const char *path = key;
            const CacheEntry *entry = value;

            if (object->parent_id != 0) {
              if (object->parent_id == entry->id && object->storage_id == entry->storage) {
                parent_path = path;
                break;
              }
            } else {
              if (entry->id == -1 && object->storage_id == entry->storage) {
                parent_path = path;
                break;
              }
            }
          }

          if (parent_path) {
            /* Create path, add cache entry and emit create event(s) */
            char *path = g_build_filename (parent_path, object->filename, NULL);

            add_cache_entry (G_VFS_BACKEND_MTP (backend),
                             path,
                             object->storage_id,
                             object->item_id);
            g_hash_table_foreach (backend->monitors, emit_create_event, path);
          }

          LIBMTP_destroy_file_t (object);
        }

        g_mutex_unlock (&backend->mutex);
        g_object_unref (backend);
        break;
      } else {
        return NULL;
      }
#endif
    default:
      break;
    }
  }
  return NULL;
}
#endif

static gboolean
mtp_heartbeat (GVfsBackendMtp *backend)
{
  if (g_mutex_trylock (&backend->mutex)) {
    LIBMTP_Dump_Device_Info(backend->device);
    g_mutex_unlock (&backend->mutex);
  }
  return TRUE;
}

static char *
get_dev_path_and_device_from_host (GVfsJob *job,
                                   GUdevClient *gudev_client,
                                   const char *host,
                                   GUdevDevice **device)
{
  /* turn usb:001,041 string into an udev device name */
  if (!g_str_has_prefix (host, "[usb:")) {
    g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
                              G_IO_ERROR_NOT_SUPPORTED,
                              _("Unexpected host URI format."));
    return NULL;
  }

  char *comma;
  char *dev_path = g_strconcat ("/dev/bus/usb/", host + 5, NULL);
  if ((comma = strchr (dev_path, ',')) == NULL) {
    g_free (dev_path);
    g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
                              G_IO_ERROR_NOT_SUPPORTED,
                              _("Malformed host URI."));
    return NULL;
  }
  *comma = '/';
  dev_path[strlen (dev_path) -1] = '\0';
  DEBUG ("(II) get_dev_path_from_host: Parsed '%s' into device name %s\n", host, dev_path);

  /* find corresponding GUdevDevice */
  *device = g_udev_client_query_by_device_file (gudev_client, dev_path);
  if (!*device) {
    g_free (dev_path);
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("Couldn't find matching udev device."));
    return NULL;
  }

  return dev_path;
}

static void
do_mount (GVfsBackend *backend,
           GVfsJobMount *job,
           GMountSpec *mount_spec,
           GMountSource *mount_source,
           gboolean is_automount)
{
  GVfsBackendMtp *op_backend = G_VFS_BACKEND_MTP (backend);
  GUdevDevice *device;

  DEBUG ("(I) do_mount\n");

  const char *host = g_mount_spec_get (mount_spec, "host");
  DEBUG ("(I) do_mount: host=%s\n", host);
  if (host == NULL) {
    g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
                              G_IO_ERROR_FAILED, _("No device specified"));
    return;
  }

  const char *subsystems[] = {"usb", NULL};
  op_backend->gudev_client = g_udev_client_new (subsystems);
  if (op_backend->gudev_client == NULL) {
    g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR,
                              G_IO_ERROR_FAILED, _("Cannot create gudev client"));
    return;
  }

  char *dev_path = get_dev_path_and_device_from_host (G_VFS_JOB (job),
                                                      op_backend->gudev_client,
                                                      host,
                                                      &device);
  if (dev_path == NULL) {
    g_object_unref (op_backend->gudev_client);
    /* get_dev_path_from_host() sets job state. */
    return;
  }
  op_backend->dev_path = dev_path;

  op_backend->volume_name = g_vfs_get_volume_name (device, "ID_MTP");
  op_backend->volume_icon = g_vfs_get_volume_icon (device);
  op_backend->volume_symbolic_icon = g_vfs_get_volume_symbolic_icon (device);
  g_object_unref (device);

  op_backend->on_uevent_id =
    g_signal_connect_object (op_backend->gudev_client, "uevent",
                             G_CALLBACK (on_uevent), op_backend, 0);

  op_backend->file_cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);

  LIBMTP_Init ();

  get_device (backend, host, G_VFS_JOB (job));
  if (!G_VFS_JOB (job)->failed) {
    GMountSpec *mtp_mount_spec = g_mount_spec_new ("mtp");
    g_mount_spec_set (mtp_mount_spec, "host", host);
    g_vfs_backend_set_mount_spec (backend, mtp_mount_spec);
    g_mount_spec_unref (mtp_mount_spec);

    g_vfs_job_succeeded (G_VFS_JOB (job));

    op_backend->hb_id =
      g_timeout_add_seconds (900, (GSourceFunc)mtp_heartbeat, op_backend);

#if HAVE_LIBMTP_1_1_5
    GWeakRef *event_ref = g_new0 (GWeakRef, 1);
    g_weak_ref_init (event_ref, backend);
    GThread *event_thread = g_thread_new ("events", check_event, event_ref);
    /*
     * We don't need our ref to the thread, as the libmtp semantics mean
     * that in the normal case, the thread will block forever when we are
     * cleanining up before termination, so we can never join the thread.
     */
    g_thread_unref (event_thread);
#endif
  }
  DEBUG ("(I) do_mount done.\n");
}


static void
do_unmount (GVfsBackend *backend, GVfsJobUnmount *job,
            GMountUnmountFlags flags,
            GMountSource *mount_source)
{
  GVfsBackendMtp *op_backend;

  DEBUG ("(I) do_umount\n");

  op_backend = G_VFS_BACKEND_MTP (backend);

  g_mutex_lock (&op_backend->mutex);

  g_atomic_int_set (&op_backend->unmount_started, TRUE);

  /* Emit delete events to tell clients files are gone. */
  GHashTableIter iter;
  gpointer key, value;
  g_hash_table_iter_init (&iter, op_backend->file_cache);
  while (g_hash_table_iter_next (&iter, &key, &value)) {
    const char *path = key;

    g_hash_table_foreach (op_backend->monitors,
                          emit_delete_event,
                         (char *)path);
  }
  g_hash_table_unref (op_backend->file_cache);

  g_source_remove (op_backend->hb_id);
  g_signal_handler_disconnect (op_backend->gudev_client,
                               op_backend->on_uevent_id);
  g_object_unref (op_backend->gudev_client);
  g_clear_pointer (&op_backend->dev_path, g_free);
  g_clear_pointer (&op_backend->volume_name, g_free);
  g_clear_pointer (&op_backend->volume_icon, g_free);
  g_clear_pointer (&op_backend->volume_symbolic_icon, g_free);
  LIBMTP_Release_Device (op_backend->device);

  g_mutex_unlock (&op_backend->mutex);

  g_vfs_job_succeeded (G_VFS_JOB (job));

  DEBUG ("(I) do_umount done.\n");
}


/************************************************
 * 	  Queries
 * 
 */


/**
 * get_device:
 *
 * Called with backend mutex lock held.
 */
LIBMTP_mtpdevice_t *
get_device (GVfsBackend *backend, const char *id, GVfsJob *job) {
  DEBUG ("(II) get_device: %s\n", id);

  LIBMTP_mtpdevice_t *device = NULL;

  if (G_VFS_BACKEND_MTP (backend)->device != NULL) {
    DEBUG ("(II) get_device: Returning cached device %p\n", device);
    return G_VFS_BACKEND_MTP (backend)->device;
  }

  LIBMTP_raw_device_t * rawdevices;
  int numrawdevices;
  LIBMTP_error_number_t err;

  err = LIBMTP_Detect_Raw_Devices (&rawdevices, &numrawdevices);
  switch (err) {
  case LIBMTP_ERROR_NONE:
    break;
  case LIBMTP_ERROR_NO_DEVICE_ATTACHED:
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("No MTP devices found"));
    goto exit;
  case LIBMTP_ERROR_CONNECTING:
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_CONNECTION_REFUSED,
                              _("Unable to connect to MTP device"));
    goto exit;
  case LIBMTP_ERROR_MEMORY_ALLOCATION:
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_FILE_ERROR, G_FILE_ERROR_NOMEM,
                              _("Unable to allocate memory while detecting MTP devices"));
    goto exit;
  case LIBMTP_ERROR_GENERAL:
  default:
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_FAILED,
                              _("Generic libmtp error"));
    goto exit;
  }

  /* Iterate over connected MTP devices */
  int i;
  for (i = 0; i < numrawdevices; i++) {
    char *name;
    name = g_strdup_printf ("[usb:%03u,%03u]",
                            rawdevices[i].bus_location,
                            rawdevices[i].devnum);

    if (strcmp (id, name) == 0) {
      device = LIBMTP_Open_Raw_Device_Uncached (&rawdevices[i]);
      if (device == NULL) {
        g_vfs_job_failed (G_VFS_JOB (job),
                          G_IO_ERROR, G_IO_ERROR_FAILED,
                          _("Unable to open MTP device '%s'"), name);
        g_free (name);
        goto exit;
      }

      DEBUG ("(II) get_device: Storing device %s\n", name);
      G_VFS_BACKEND_MTP (backend)->device = device;

      LIBMTP_Dump_Errorstack (device);
      LIBMTP_Clear_Errorstack (device);
      g_free (name);
      break;
    } else {
      g_free (name);
    }
  }

  if (device == NULL) {
    g_vfs_job_failed (G_VFS_JOB (job),
                      G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                      _("Device not found"));
    goto exit;
  }

  /* Check supported methods/extensions. */
  LIBMTP_device_extension_t *extension;
  for (extension = device->extensions; extension != NULL; extension = extension->next) {
    if (g_strcmp0 ("android.com", extension->name) == 0) {
      G_VFS_BACKEND_MTP (backend)->android_extension = TRUE;
      break;
    }
  }

 exit:
  DEBUG ("(II) get_device done.\n");
  return device;
}


/**
 * get_device_info:
 *
 * Called with backend mutex lock held.
 */
static void
get_device_info (GVfsBackendMtp *backend, GFileInfo *info)
{
  LIBMTP_mtpdevice_t *device = backend->device;
  const char *name;

  name = g_mount_spec_get (g_vfs_backend_get_mount_spec (G_VFS_BACKEND (backend)), "host");

  DEBUG_ENUMERATE ("(II) get_device_info: %s\n", name);

  g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
  g_file_info_set_name (info, name);

  char *friendlyname = LIBMTP_Get_Friendlyname (device);
  g_file_info_set_display_name (info, friendlyname == NULL ?
                                      backend->volume_name : friendlyname);
  free (friendlyname);

  g_file_info_set_content_type (info, "inode/directory");
  g_file_info_set_size (info, 0);

  GIcon *icon = g_themed_icon_new (backend->volume_icon);
  g_file_info_set_icon (info, icon);
  g_object_unref (icon);

  icon = g_themed_icon_new (backend->volume_symbolic_icon);
  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, 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_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "mtpfs");

  int ret = LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED);
  if (ret != 0) {
    LIBMTP_Dump_Errorstack (device);
    LIBMTP_Clear_Errorstack (device);
    DEBUG_ENUMERATE ("(II) get_device_info done with no stores.\n");
    return;
  }
  guint64 freeSpace = 0;
  guint64 maxSpace = 0;
  LIBMTP_devicestorage_t *storage;
  for (storage = device->storage; storage != 0; storage = storage->next) {
    freeSpace += storage->FreeSpaceInBytes;
    maxSpace += storage->MaxCapacity;
  }

  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, freeSpace);
  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, maxSpace);

  DEBUG_ENUMERATE ("(II) get_device_info done.\n");
}


/**
 * get_storage_info:
 *
 * Called with backend mutex lock held.
 */
static void
get_storage_info (LIBMTP_devicestorage_t *storage, GFileInfo *info) {

  DEBUG_ENUMERATE ("(II) get_storage_info: %X\n", storage->id);

  char *storage_name = create_storage_name(storage);
  g_file_info_set_name (info, storage_name);
  g_file_info_set_display_name (info, storage_name);
  g_free(storage_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);

  GIcon *icon;
  switch (storage->StorageType) {
  case PTP_ST_FixedROM:
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE);
    icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk");
    break;
  case PTP_ST_RemovableROM:
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, TRUE);
    icon = g_themed_icon_new_with_default_fallbacks ("media-flash-sd");
    break;
  case PTP_ST_RemovableRAM:
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, FALSE);
    icon = g_themed_icon_new_with_default_fallbacks ("media-flash-sd");
    break;
  case PTP_ST_FixedRAM:
  default:
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, FALSE);
    icon = g_themed_icon_new_with_default_fallbacks ("drive-harddisk");
    break;
  }
  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, TRUE);
  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_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, storage->FreeSpaceInBytes);
  g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, storage->MaxCapacity);
  g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, "mtpfs");

  DEBUG_ENUMERATE ("(II) get_storage_info done.\n");
}


/**
 * get_file_info:
 *
 * Called with backend mutex lock held.
 */
static void
get_file_info (GVfsBackend *backend,
               LIBMTP_mtpdevice_t *device,
               GFileInfo *info,
               LIBMTP_file_t *file) {
  GIcon *icon = NULL;
  char *content_type = NULL;

  DEBUG_ENUMERATE ("(II) get_file_info: %X\n", file->item_id);

  g_file_info_set_name (info, file->filename);
  g_file_info_set_display_name (info, file->filename);

  switch (file->filetype) {
  case LIBMTP_FILETYPE_FOLDER:
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, TRUE);
    g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY);
    g_file_info_set_content_type (info, "inode/directory");
    icon = g_themed_icon_new ("folder");
    break;
  default:
    g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, FALSE);
    g_file_info_set_file_type (info, G_FILE_TYPE_REGULAR);
    content_type = g_content_type_guess (file->filename, NULL, 0, NULL);
    g_file_info_set_content_type (info, content_type);
    icon = g_content_type_get_icon (content_type);
    break;
  }


#if HAVE_LIBMTP_1_1_5
  if (LIBMTP_FILETYPE_IS_IMAGE (file->filetype) ||
      LIBMTP_FILETYPE_IS_VIDEO (file->filetype) ||
      LIBMTP_FILETYPE_IS_AUDIOVIDEO (file->filetype)) {

    GIcon *preview;
    char *icon_id;
    GMountSpec *mount_spec;

    mount_spec = g_vfs_backend_get_mount_spec (backend);
    icon_id = g_strdup_printf ("%X", file->item_id);
    preview = g_vfs_icon_new (mount_spec,
                              icon_id);
    g_file_info_set_attribute_object (info,
                                      G_FILE_ATTRIBUTE_PREVIEW_ICON,
                                      G_OBJECT (preview));
    g_object_unref (preview);
    g_free (icon_id);
  }
#endif

  g_file_info_set_size (info, file->filesize);

  GTimeVal modtime = { file->modificationdate, 0 };
  g_file_info_set_modification_time (info, &modtime);

  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, TRUE);
  g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, 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, TRUE);


  if (icon != NULL) {
    g_file_info_set_icon (info, icon);
    g_object_unref (icon);
  }
  g_free (content_type);

  DEBUG_ENUMERATE ("(II) get_file_info done.\n");
}


static void
do_enumerate (GVfsBackend *backend,
              GVfsJobEnumerate *job,
              const char *filename,
              GFileAttributeMatcher *attribute_matcher,
              GFileQueryInfoFlags flags)
{
  GVfsBackendMtp *op_backend = G_VFS_BACKEND_MTP (backend);
  GFileInfo *info;

  gchar **elements = g_strsplit_set (filename, "/", -1);
  unsigned int ne = g_strv_length (elements);

  DEBUG ("(I) do_enumerate (filename = %s, n_elements = %d)\n", filename, ne);

  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  LIBMTP_mtpdevice_t *device;
  device = op_backend->device;

  if (ne == 2 && elements[1][0] == '\0') {
    LIBMTP_devicestorage_t *storage;

    int ret = LIBMTP_Get_Storage (device, LIBMTP_STORAGE_SORTBY_NOTSORTED);
    if (ret != 0) {
      LIBMTP_Dump_Errorstack (device);
      LIBMTP_Clear_Errorstack (device);
      goto success;
    }
    for (storage = device->storage; storage != 0; storage = storage->next) {
      info = g_file_info_new ();
      get_storage_info (storage, info);
      g_vfs_job_enumerate_add_info (job, info);
      g_object_unref (info);

      char *storage_name = create_storage_name (storage);
      add_cache_entry (G_VFS_BACKEND_MTP (backend),
                       g_build_filename (filename, storage_name, NULL),
                       storage->id,
                       -1);

      g_free (storage_name);
    }
  } else {
    CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend),
                                         filename);
    if (entry == NULL) {
      LIBMTP_Dump_Errorstack (device);
      LIBMTP_Clear_Errorstack (device);
      g_vfs_job_failed_literal (G_VFS_JOB (job),
                                G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                _("File not found"));
      goto exit;
    }

    /* Invalidate existing cache entries in case any are stale. */
    char *remove_prefix = g_strdup_printf("%s/", filename);
    remove_cache_entry (G_VFS_BACKEND_MTP (backend), remove_prefix);
    g_free (remove_prefix);

    LIBMTP_file_t *files;
    LIBMTP_Clear_Errorstack (device);
    files = LIBMTP_Get_Files_And_Folders (device, entry->storage, entry->id);
    if (files == NULL && LIBMTP_Get_Errorstack (device) != NULL) {
      fail_job (G_VFS_JOB (job), device);
      goto exit;
    }
    while (files != NULL) {
      LIBMTP_file_t *file = files;
      files = files->next;

      info = g_file_info_new ();
      get_file_info (backend, device, info, file);
      g_vfs_job_enumerate_add_info (job, info);
      g_object_unref (info);

      add_cache_entry (G_VFS_BACKEND_MTP (backend),
                       g_build_filename (filename, file->filename, NULL),
                       file->storage_id,
                       file->item_id);

      LIBMTP_destroy_file_t (file);
    }
  }

 success:
  g_vfs_job_enumerate_done (job);
  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_strfreev (elements);
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);
  DEBUG ("(I) do_enumerate done.\n");
}


static void
do_query_info (GVfsBackend *backend,
               GVfsJobQueryInfo *job,
               const char *filename,
               GFileQueryInfoFlags flags,
               GFileInfo *info,
               GFileAttributeMatcher *matcher)
{
  DEBUG ("(I) do_query_info (filename = %s)\n", filename);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  gchar **elements = g_strsplit_set (filename, "/", -1);
  unsigned int ne = g_strv_length (elements);

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  if (ne == 2 && elements[1][0] == '\0') {
    get_device_info (G_VFS_BACKEND_MTP (backend), info);
  } else if (ne < 3) {
    CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend),
                                         filename);
    if (!entry) {
      LIBMTP_Dump_Errorstack (device);
      LIBMTP_Clear_Errorstack (device);
      g_vfs_job_failed_literal (G_VFS_JOB (job),
                                G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                _("Directory doesn't exist"));
      goto exit;
    }

    LIBMTP_devicestorage_t *storage;
    gboolean found = FALSE;
    for (storage = device->storage; storage != 0; storage = storage->next) {
      if (storage->id == entry->storage) {
        DEBUG ("(I) found storage %X\n", storage->id);
        found = TRUE;
        get_storage_info (storage, info);
        break;
      }
    }

    if (!found) {
      DEBUG ("(W) storage %X not found?!\n", entry->storage);
      g_vfs_job_failed_literal (G_VFS_JOB (job),
                                G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                _("Directory doesn't exist"));
      goto exit;
    }
  } else {
    CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend),
                                         filename);
    if (!entry) {
      LIBMTP_Dump_Errorstack (device);
      LIBMTP_Clear_Errorstack (device);
      g_vfs_job_failed_literal (G_VFS_JOB (job),
                                G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                _("File not found"));
      goto exit;
    }


    LIBMTP_file_t *file = NULL;
    file = LIBMTP_Get_Filemetadata (device, entry->id);

    if (file != NULL) {
      get_file_info (backend, device, info, file);
      LIBMTP_destroy_file_t (file);
    } else {
      fail_job (G_VFS_JOB (job), device);
      goto exit;
    }
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_strfreev (elements);
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);
  DEBUG ("(I) do_query_info done.\n");
}


static void
do_query_fs_info (GVfsBackend *backend,
		  GVfsJobQueryFsInfo *job,
		  const char *filename,
		  GFileInfo *info,
		  GFileAttributeMatcher *attribute_matcher)
{
  DEBUG ("(I) do_query_fs_info (filename = %s)\n", filename);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  gchar **elements = g_strsplit_set (filename, "/", -1);
  unsigned int ne = g_strv_length (elements);

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  if (ne == 2 && elements[1][0] == '\0') {
    get_device_info (G_VFS_BACKEND_MTP (backend), info);
  } else {
    CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend),
                                         filename);
    if (entry == NULL) {
      LIBMTP_Dump_Errorstack (device);
      LIBMTP_Clear_Errorstack (device);
      g_vfs_job_failed_literal (G_VFS_JOB (job),
                                G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                                _("File not found"));
      goto exit;
    }

    LIBMTP_devicestorage_t *storage;
    for (storage = device->storage; storage != 0; storage = storage->next) {
      if (storage->id == entry->storage) {
        get_storage_info (storage, info);
      }
    }
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_strfreev (elements);
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_query_fs_info done.\n");
}


/************************************************
 * 	  Operations
 * 
 */

typedef struct {
  GFileProgressCallback progress_callback;
  gpointer progress_callback_data;
  GVfsJob *job;
} MtpProgressData;


static int
mtp_progress (uint64_t const sent, uint64_t const total,
              MtpProgressData const * const data)
{
  if (data->progress_callback) {
    data->progress_callback (sent, total, data->progress_callback_data);
  }
  return g_vfs_job_is_cancelled (data->job);
}


static void
do_make_directory (GVfsBackend *backend,
                   GVfsJobMakeDirectory *job,
                   const char *filename)
{
  DEBUG ("(I) do_make_directory (filename = %s)\n", filename);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  char *dir_name = g_path_get_dirname (filename);
  char *base_name = g_path_get_basename (filename);

  gchar **elements = g_strsplit_set (filename, "/", -1);
  unsigned int ne = g_strv_length (elements);

  if (ne < 3) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                              _("Cannot make directory in this location"));
    goto exit;
  }

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), filename);
  if (entry != NULL && entry->id != -1) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_EXISTS,
                              _("Target file already exists"));
    goto exit;
  }

  entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), dir_name);
  if (!entry) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("Directory doesn't exist"));
    goto exit;
  }

  int ret = LIBMTP_Create_Folder (device, base_name, entry->id, entry->storage);
  if (ret == 0) {
    fail_job (G_VFS_JOB (job), device);
    goto exit;
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));

  g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors, emit_create_event, (char *)filename);

 exit:
  g_strfreev (elements);
  g_free (dir_name);
  g_free (base_name);
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_make_directory done.\n");
}


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)
{
  DEBUG ("(I) do_pull (filename = %s, local_path = %s)\n", source, local_path);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  GFile *local_file = NULL;
  GFileInfo *local_info = NULL;
  GFileInfo *info = NULL;

  CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), source);
  if (entry == NULL) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("File does not exist"));
    goto exit;
  } else if (entry->id == -1) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE,
                              _("Not a regular file"));
    goto exit;
  }

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, entry->id);
  if (file == NULL) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("File does not exist"));
    goto exit;
  }

  info = g_file_info_new ();
  get_file_info (backend, device, info, file);
  LIBMTP_destroy_file_t (file);

  local_file = g_file_new_for_path (local_path);

  gboolean source_is_dir =
    g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;

  GError *error = NULL;
  local_info =
    g_file_query_info (local_file, G_FILE_ATTRIBUTE_STANDARD_TYPE,
                       0, G_VFS_JOB (job)->cancellable, &error);
  gboolean dest_exists = local_info || error->code != G_IO_ERROR_NOT_FOUND;
  if (!local_info && error->code != G_IO_ERROR_NOT_FOUND) {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
    g_error_free (error);
    goto exit;
  } else if (error != NULL) {
    g_error_free (error);
  }

  /* Test all the GIO defined failure conditions */
  if (dest_exists) {
    gboolean dest_is_dir =
      g_file_info_get_file_type (local_info) == G_FILE_TYPE_DIRECTORY;

    if (flags & G_FILE_COPY_OVERWRITE) {
      if (!source_is_dir && dest_is_dir) {
        g_vfs_job_failed_literal (G_VFS_JOB (job),
                                  G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                                  _("Target is a directory"));
        goto exit;
      } else if (source_is_dir && dest_is_dir) {
        g_vfs_job_failed_literal (G_VFS_JOB (job),
                                  G_IO_ERROR, G_IO_ERROR_WOULD_MERGE,
                                  _("Can't merge directories"));
        goto exit;
      } else if (source_is_dir && !dest_is_dir) {
        g_vfs_job_failed_literal (G_VFS_JOB (job),
                                  G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
                                  _("Can't recursively copy directory"));
        goto exit;
      }
      /* Source and Dest are files */
      DEBUG ("(I) Removing destination.\n");
      GError *error = NULL;
      gboolean ret = g_file_delete (local_file,
                                    G_VFS_JOB (job)->cancellable,
                                    &error);
      if (!ret) {
        g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
        g_error_free (error);
        goto exit;
      }
    } else {
      g_vfs_job_failed_literal (G_VFS_JOB (job),
                                G_IO_ERROR, G_IO_ERROR_EXISTS,
                                _("Target file already exists"));
      goto exit;
    }
  } else if (source_is_dir) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
                              _("Can't recursively copy directory"));
    goto exit;
  }

  MtpProgressData mtp_progress_data;
  mtp_progress_data.progress_callback = progress_callback;
  mtp_progress_data.progress_callback_data = progress_callback_data;
  mtp_progress_data.job = G_VFS_JOB (job);
  int ret = LIBMTP_Get_File_To_File (device,
                                     entry->id,
                                     local_path,
                                     (LIBMTP_progressfunc_t)mtp_progress,
                                     &mtp_progress_data);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), device);
    goto exit;
  }
  /* Attempt to delete object if requested but don't fail it it fails. */
  if (remove_source) {
    DEBUG ("(I) Removing source.\n");
    LIBMTP_Delete_Object (device, entry->id);
    g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors,
                          emit_delete_event,
                          (char *)source);
    remove_cache_entry (G_VFS_BACKEND_MTP (backend),
                        source);
  }
  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_clear_object (&local_info);
  g_clear_object (&local_file);
  g_clear_object (&info);
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_pull done.\n");
}


static LIBMTP_filetype_t
get_filetype_from_info (GFileInfo *info) {
  const char *content_type = g_file_info_get_content_type (info);
  DEBUG ("(II) get_filetype_from_info (info = %s)\n", content_type);

  LIBMTP_filetype_t ret;
  if (!content_type) {
    ret = LIBMTP_FILETYPE_UNKNOWN;
  } else if (strcmp (content_type, "audio/wav") == 0 ||
             strcmp (content_type, "audio/x-wav") == 0 ||
             strcmp (content_type, "audio/vnd.wave") == 0) {
    ret = LIBMTP_FILETYPE_WAV;
  } else if (strcmp (content_type, "audio/mpeg") == 0 ||
             strcmp (content_type, "audio/x-mp3") == 0 ||
             strcmp (content_type, "audio/x-mpeg") == 0 ||
             strcmp (content_type, "audio/mp3") == 0) {
    ret = LIBMTP_FILETYPE_MP3;
  } else if (strcmp (content_type, "audio/x-ms-wma") == 0 ||
             strcmp (content_type, "audio/wma") == 0) {
    ret = LIBMTP_FILETYPE_WMA;
  } else if (strcmp (content_type, "audio/ogg") == 0 ||
             strcmp (content_type, "audio/x-ogg") == 0) {
    ret = LIBMTP_FILETYPE_OGG;
  } else if (strcmp (content_type, "audio/audible") == 0 ||
             strcmp (content_type, "audio/x-pn-audibleaudio") == 0) {
    ret = LIBMTP_FILETYPE_AUDIBLE;
  } else if (strcmp (content_type, "video/mp4") == 0 ||
             strcmp (content_type, "video/x-m4v") == 0 ||
             strcmp (content_type, "video/mp4v-es") == 0) {
    ret = LIBMTP_FILETYPE_MP4;
  } else if (strcmp (content_type, "video/x-ms-wmv") == 0) {
    ret = LIBMTP_FILETYPE_WMV;
  } else if (strcmp (content_type, "video/x-msvideo") == 0 ||
             strcmp (content_type, "video/x-avi") == 0 ||
             strcmp (content_type, "video/avi") == 0 ||
             strcmp (content_type, "video/divx") == 0 ||
             strcmp (content_type, "video/msvideo") == 0 ||
             strcmp (content_type, "video/vnd.divx") == 0) {
    ret = LIBMTP_FILETYPE_AVI;
  } else if (strcmp (content_type, "video/mpeg") == 0 ||
             strcmp (content_type, "video/x-mpeg") == 0 ||
             strcmp (content_type, "video/x-mpeg2") == 0) {
    ret = LIBMTP_FILETYPE_MPEG;
  } else if (strcmp (content_type, "video/x-ms-asf") == 0 ||
             strcmp (content_type, "video/x-ms-wm") == 0 ||
             strcmp (content_type, "video/vnd.ms-asf") == 0) {
    ret = LIBMTP_FILETYPE_ASF;
  } else if (strcmp (content_type, "video/quicktime") == 0) {
    ret = LIBMTP_FILETYPE_QT;
  } else if (strcmp (content_type, "image/jpeg") == 0 ||
             strcmp (content_type, "image/pjpeg") == 0) {
    ret = LIBMTP_FILETYPE_JPEG;
  } else if (strcmp (content_type, "image/tiff") == 0) {
    ret = LIBMTP_FILETYPE_TIFF;
  } else if (strcmp (content_type, "image/bmp") == 0 ||
             strcmp (content_type, "image/x-bmp") == 0 ||
             strcmp (content_type, "image/x-MS-bmp") == 0) {
    ret = LIBMTP_FILETYPE_BMP;
  } else if (strcmp (content_type, "image/gif") == 0) {
    ret = LIBMTP_FILETYPE_GIF;
  } else if (strcmp (content_type, "image/x-pict") == 0) {
    ret = LIBMTP_FILETYPE_PICT;
  } else if (strcmp (content_type, "image/png") == 0) {
    ret = LIBMTP_FILETYPE_PNG;
  } else if (strcmp (content_type, "text/x-vcalendar") == 0) {
    ret = LIBMTP_FILETYPE_VCALENDAR1;
  } else if (strcmp (content_type, "text/calendar") == 0 ||
             strcmp (content_type, "application/ics") == 0) {
    ret = LIBMTP_FILETYPE_VCALENDAR2;
  } else if (strcmp (content_type, "text/x-vcard") == 0 ||
             strcmp (content_type, "text/directory") == 0) {
    ret = LIBMTP_FILETYPE_VCARD2;
  } else if (strcmp (content_type, "text/vcard") == 0) {
    ret = LIBMTP_FILETYPE_VCARD3;
  } else if (strcmp (content_type, "image/x-wmf") == 0 ||
             strcmp (content_type, "image/wmf") == 0 ||
             strcmp (content_type, "image/x-win-metafile") == 0 ||
             strcmp (content_type, "application/x-wmf") == 0 ||
             strcmp (content_type, "application/wmf") == 0 ||
             strcmp (content_type, "application/x-msmetafile") == 0) {
    ret = LIBMTP_FILETYPE_WINDOWSIMAGEFORMAT;
  } else if (strcmp (content_type, "application/x-ms-dos-executable") == 0) {
    ret = LIBMTP_FILETYPE_WINEXEC;
  } else if (strcmp (content_type, "text/plain") == 0) {
    ret = LIBMTP_FILETYPE_TEXT;
  } else if (strcmp (content_type, "text/html") == 0) {
    ret = LIBMTP_FILETYPE_HTML;
  } else if (strcmp (content_type, "audio/aac") == 0) {
    ret = LIBMTP_FILETYPE_AAC;
  } else if (strcmp (content_type, "audio/flac") == 0 ||
             strcmp (content_type, "audio/x-flac") == 0 ||
             strcmp (content_type, "audio/x-flac+ogg") == 0 ||
             strcmp (content_type, "audio/x-oggflac") == 0) {
    ret = LIBMTP_FILETYPE_FLAC;
  } else if (strcmp (content_type, "audio/mp2") == 0 ||
             strcmp (content_type, "audio/x-mp2") == 0) {
    ret = LIBMTP_FILETYPE_MP2;
  } else if (strcmp (content_type, "audio/mp4") == 0 ||
             strcmp (content_type, "audio/x-m4a") == 0) {
    ret = LIBMTP_FILETYPE_M4A;
  } else if (strcmp (content_type, "application/msword") == 0 ||
             strcmp (content_type, "application/vnd.ms-word") == 0 ||
             strcmp (content_type, "application/x-msword") == 0 ||
             strcmp (content_type, "zz-application/zz-winassoc-doc") == 0) {
    ret = LIBMTP_FILETYPE_DOC;
  } else if (strcmp (content_type, "text/xml") == 0 ||
             strcmp (content_type, "application/xml") == 0) {
    ret = LIBMTP_FILETYPE_XML;
  } else if (strcmp (content_type, "application/msexcel") == 0 ||
             strcmp (content_type, "application/vnd.ms-excel") == 0 ||
             strcmp (content_type, "application/x-msexcel") == 0 ||
             strcmp (content_type, "zz-application/zz-winassoc-xls") == 0) {
    ret = LIBMTP_FILETYPE_XLS;
  } else if (strcmp (content_type, "application/mspowerpoint") == 0 ||
             strcmp (content_type, "application/vnd.ms-powerpoint") == 0 ||
             strcmp (content_type, "application/x-mspowerpoint") == 0 ||
             strcmp (content_type, "application/powerpoint") == 0) {
    ret = LIBMTP_FILETYPE_PPT;
  } else if (strcmp (content_type, "message/rfc822") == 0) {
    ret = LIBMTP_FILETYPE_MHT;
  } else if (strcmp (content_type, "image/jp2") == 0) {
    ret = LIBMTP_FILETYPE_JP2;
  } else if (strcmp (content_type, "image/jpx") == 0) {
    ret = LIBMTP_FILETYPE_JPX;
  } else if (strcmp (content_type, "audio/x-mpegurl") == 0 ||
             strcmp (content_type, "audio/mpegurl") == 0 ||
             strcmp (content_type, "application/m3u") == 0 ||
             strcmp (content_type, "audio/x-mp3-playlist") == 0 ||
             strcmp (content_type, "audio/m3u") == 0 ||
             strcmp (content_type, "audio/x-m3u") == 0) {
    ret = LIBMTP_FILETYPE_PLAYLIST;
  } else if (strncmp (content_type, "audio/", 6) == 0) {
    // Must come after all other audio types.
    ret = LIBMTP_FILETYPE_UNDEF_AUDIO;
  } else if (strncmp (content_type, "video/", 6) == 0) {
    // Must come after all other video types.
    ret = LIBMTP_FILETYPE_UNDEF_VIDEO;
  } else {
    ret = LIBMTP_FILETYPE_UNKNOWN;
  }

  /* Unmappable Types
    LIBMTP_FILETYPE_JFIF,
    LIBMTP_FILETYPE_FIRMWARE,
    LIBMTP_FILETYPE_MEDIACARD,
    LIBMTP_FILETYPE_ALBUM,
  */

  DEBUG ("(II) get_filetype_from_info done.\n");

  return ret;
}


static void
do_push (GVfsBackend *backend,
         GVfsJobPush *job,
         const char *destination,
         const char *local_path,
         GFileCopyFlags flags,
         gboolean remove_source,
         GFileProgressCallback progress_callback,
         gpointer progress_callback_data)
{
  DEBUG ("(I) do_push (filename = %s, local_path = %s)\n", destination, local_path);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  char *dir_name = g_path_get_dirname (destination);
  char *filename = g_path_get_basename (destination);

  GFile *local_file = NULL;
  GFileInfo *info = NULL;
  gchar **elements = g_strsplit_set (destination, "/", -1);
  unsigned int ne = g_strv_length (elements);

  if (ne < 3) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                              _("Cannot write to this location"));
    goto exit;
  }

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), destination);
  gboolean dest_exists = entry != NULL && entry->id != -1;
  gboolean dest_is_dir = FALSE;

  if (dest_exists) {
    LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, entry->id);
    if (file != NULL) {
      dest_is_dir = file->filetype == LIBMTP_FILETYPE_FOLDER;
      LIBMTP_destroy_file_t (file);
    }
  }

  CacheEntry *parent = get_cache_entry (G_VFS_BACKEND_MTP (backend), dir_name);
  if (!parent) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("Directory doesn't exist"));
    goto exit;
  }

  GError *error = NULL;
  local_file = g_file_new_for_path (local_path);
  info =
    g_file_query_info (local_file,
                       G_FILE_ATTRIBUTE_STANDARD_TYPE","
                       G_FILE_ATTRIBUTE_STANDARD_SIZE","
                       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
                       0, G_VFS_JOB (job)->cancellable, &error);
  if (!info) {
    g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
    g_error_free (error);
    goto exit;
  }

  /*
   * Don't do this for >=4GB files with android extensions. Some devices
   * have trouble transferring large files this way. eg: Nexus 5.
   */
  if (g_file_info_get_size (info) > G_MAXUINT32 &&
      G_VFS_BACKEND_MTP (backend)->android_extension) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                              _("Operation unsupported"));
    goto exit;
  }

  gboolean source_is_dir =
    g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY;

  /* Test all the GIO defined failure conditions */
  if (dest_exists) {
    if (flags & G_FILE_COPY_OVERWRITE) {
      if (!source_is_dir && dest_is_dir) {
        g_vfs_job_failed_literal (G_VFS_JOB (job),
                                  G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                                  _("Target is a directory"));
        goto exit;
      } else if (source_is_dir && dest_is_dir) {
        g_vfs_job_failed_literal (G_VFS_JOB (job),
                                  G_IO_ERROR, G_IO_ERROR_WOULD_MERGE,
                                  _("Can't merge directories"));
        goto exit;
      } else if (source_is_dir && !dest_is_dir) {
        g_vfs_job_failed_literal (G_VFS_JOB (job),
                                  G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
                                  _("Can't recursively copy directory"));
        goto exit;
      }
      /* Source and Dest are files */
      DEBUG ("(I) Removing destination.\n");
      int ret = LIBMTP_Delete_Object (device, entry->id);
      if (ret != 0) {
        fail_job (G_VFS_JOB (job), device);
        goto exit;
      }
      g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors,
                            emit_delete_event,
                            (char *)destination);
      remove_cache_entry (G_VFS_BACKEND_MTP (backend),
                          destination);
    } else {
      g_vfs_job_failed_literal (G_VFS_JOB (job),
                                G_IO_ERROR, G_IO_ERROR_EXISTS,
                                _("Target file already exists"));
      goto exit;
    }
  } else if (source_is_dir) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_WOULD_RECURSE,
                              _("Can't recursively copy directory"));
    goto exit;
  }

  LIBMTP_file_t *mtpfile = LIBMTP_new_file_t ();
  mtpfile->filename = strdup (filename);
  mtpfile->parent_id = parent->id;
  mtpfile->storage_id = parent->storage;
  mtpfile->filetype = get_filetype_from_info (info);
  mtpfile->filesize = g_file_info_get_size (info);

  MtpProgressData mtp_progress_data;
  mtp_progress_data.progress_callback = progress_callback;
  mtp_progress_data.progress_callback_data = progress_callback_data;
  mtp_progress_data.job = G_VFS_JOB (job);
  int ret = LIBMTP_Send_File_From_File (device, local_path, mtpfile,
                                        (LIBMTP_progressfunc_t)mtp_progress,
                                        &mtp_progress_data);
  LIBMTP_destroy_file_t (mtpfile);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), device);
    goto exit;
  }

  /* Attempt to delete object if requested but don't fail it it fails. */
  if (remove_source) {
    DEBUG ("(I) Removing source.\n");
    g_file_delete (local_file, G_VFS_JOB (job)->cancellable, NULL);
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));

  g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors,
                        emit_create_event,
                        (char *)destination);

 exit:
  g_clear_object (&local_file);
  g_clear_object (&info);
  g_strfreev (elements);
  g_free (dir_name);
  g_free (filename);
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_push done.\n");
}


static void
do_delete (GVfsBackend *backend,
            GVfsJobDelete *job,
            const char *filename)
{
  DEBUG ("(I) do_delete (filename = %s)\n", filename);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), filename);
  if (entry == NULL) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("File does not exist"));
    goto exit;
  } else if (entry->id == -1) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                              _("Not a regular file"));
    goto exit;
  }

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  int ret = LIBMTP_Delete_Object (device, entry->id);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), device);
    goto exit;
  }
  g_vfs_job_succeeded (G_VFS_JOB (job));

  g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors,
                        emit_delete_event,
                        (char *)filename);
  remove_cache_entry (G_VFS_BACKEND_MTP (backend),
                      filename);

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_delete done.\n");
}


static void
do_set_display_name (GVfsBackend *backend,
                      GVfsJobSetDisplayName *job,
                      const char *filename,
                      const char *display_name)
{
  DEBUG ("(I) do_set_display_name '%s' --> '%s'\n", filename, display_name);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), filename);
  if (entry == NULL) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("File does not exist"));
    goto exit;
  } else if (entry->id == -1) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                              _("Not a regular file"));
    goto exit;
  }

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, entry->id);
  int ret = LIBMTP_Set_File_Name (device, file, display_name);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), device);
    goto exit;
  }

  char *dir_name = g_path_get_dirname (filename);
  char *new_name = g_build_filename (dir_name, display_name, NULL);

  remove_cache_entry (G_VFS_BACKEND_MTP (backend),
                      filename);
  add_cache_entry (G_VFS_BACKEND_MTP (backend), new_name, file->storage_id, file->item_id);

  LIBMTP_destroy_file_t (file);
  file = NULL;

  g_vfs_job_set_display_name_set_new_path (job, new_name);
  g_vfs_job_succeeded (G_VFS_JOB (job));

  g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors,
                        emit_create_event,
                        (char *)new_name);
  g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors,
                        emit_delete_event,
                        (char *)filename);
  g_free (dir_name);

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_set_display_name done.\n");
}


#if HAVE_LIBMTP_1_1_6
static void
do_open_for_read (GVfsBackend *backend,
                  GVfsJobOpenForRead *job,
                  const char *filename)
{
  if (!G_VFS_BACKEND_MTP (backend)->android_extension) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                              _("Operation unsupported"));
    return;
  }

  DEBUG ("(I) do_open_for_read (%s)\n", filename);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), filename);
  if (entry == NULL) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("File does not exist"));
    goto exit;
  } else if (entry->id == -1) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                              _("Not a regular file"));
    goto exit;
  }

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, entry->id);
  if (file == NULL) {
    fail_job (G_VFS_JOB (job), device);
    goto exit;
  }

  if (file->filetype == LIBMTP_FILETYPE_FOLDER) {
    LIBMTP_destroy_file_t (file);
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY,
                             _("Can't open directory"));
    goto exit;
  }

  RWHandle *handle = g_new0(RWHandle, 1);
  handle->handle_type = HANDLE_FILE;
  handle->id = entry->id;
  handle->offset = 0;
  handle->size = file->filesize;

  LIBMTP_destroy_file_t (file);

  g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), TRUE);
  g_vfs_job_open_for_read_set_handle (G_VFS_JOB_OPEN_FOR_READ (job), handle);
  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_open_for_read done.\n");
}
#endif /* HAVE_LIBMTP_1_1_6 */


#if HAVE_LIBMTP_1_1_5
static void
do_open_icon_for_read (GVfsBackend *backend,
                       GVfsJobOpenIconForRead *job,
                       const char *icon_id)
{
  DEBUG ("(I) do_open_icon_for_read (%s)\n", icon_id);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  guint id = strtol (icon_id, NULL, 16);

  if (id > 0) {
    GByteArray *bytes = NULL;
    unsigned char *data;
    unsigned int size;
    int ret;

    ret = LIBMTP_Get_Thumbnail (G_VFS_BACKEND_MTP (backend)->device, id,
                                &data, &size);
    if (ret == 0) {
      DEBUG ("File %X has thumbnail: %u\n", id, size);
      if (size > 0) {
        bytes = g_byte_array_sized_new (size);
        g_byte_array_append (bytes, data, size);
      }
      free (data);
    }

    if (!bytes) {
      LIBMTP_filesampledata_t *sample_data = LIBMTP_new_filesampledata_t ();
      ret = LIBMTP_Get_Representative_Sample (G_VFS_BACKEND_MTP (backend)->device,
                                              id, sample_data);
      if (ret == 0) {
        DEBUG ("File %X has sampledata: %u\n", id, sample_data->size);
        if (sample_data->size > 0) {
          bytes = g_byte_array_sized_new (sample_data->size);
          g_byte_array_append (bytes, (const guint8 *)sample_data->data, sample_data->size);
          size = sample_data->size;
        }
      }
      LIBMTP_destroy_filesampledata_t (sample_data);
    }

    if (!bytes) {
      DEBUG ("File %X has no thumbnail or sampledata\n", id);
      g_vfs_job_failed (G_VFS_JOB (job),
                        G_IO_ERROR,
                        G_IO_ERROR_NOT_FOUND,
                        _("No thumbnail for entity '%s'"),
                        icon_id);
      goto exit;
    }

    RWHandle *handle = g_new0(RWHandle, 1);
    handle->handle_type = HANDLE_PREVIEW;
    handle->id = id;
    handle->offset = 0;
    handle->size = size;
    handle->bytes = bytes;
    g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), TRUE);
    g_vfs_job_open_for_read_set_handle (G_VFS_JOB_OPEN_FOR_READ (job), handle);
    g_vfs_job_succeeded (G_VFS_JOB (job));
  } else {
    g_vfs_job_failed (G_VFS_JOB (job),
                      G_IO_ERROR,
                      G_IO_ERROR_INVALID_ARGUMENT,
                      _("Malformed icon identifier '%s'"),
                      icon_id);
    goto exit;
  }

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_open_icon_for_read done.\n");
}
#endif /* HAVE_LIBMTP_1_1_5 */


static void
do_seek_on_read (GVfsBackend *backend,
                 GVfsJobSeekRead *job,
                 GVfsBackendHandle opaque_handle,
                 goffset    offset,
                 GSeekType  type)
{
  RWHandle *handle = opaque_handle;
  uint32_t id = handle->id;
  goffset old_offset = handle->offset;
  gsize size = handle->size;

  DEBUG ("(I) do_seek_on_read (%X %lu %ld %u)\n", id, old_offset, offset, type);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  if (type == G_SEEK_END) {
    offset = size + offset;
  } else if (type == G_SEEK_CUR) {
    offset += old_offset;
  }

  if (offset < 0) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
                              _("End of stream"));
    goto exit;
  }

  handle->offset = offset;
  g_vfs_job_seek_read_set_offset (job, offset);
  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);
  DEBUG ("(I) do_seek_on_read done. (%lu)\n", offset);
}


static void
do_read (GVfsBackend *backend,
         GVfsJobRead *job,
         GVfsBackendHandle opaque_handle,
         char *buffer,
         gsize bytes_requested)
{
  RWHandle *handle = opaque_handle;
  uint32_t id = handle->id;
  goffset offset = handle->offset;

  DEBUG ("(I) do_read (%X %lu %lu)\n", id, offset, bytes_requested);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  uint32_t actual;
  if (handle->handle_type == HANDLE_FILE) {
#if HAVE_LIBMTP_1_1_6
    unsigned char *temp;
    int ret = LIBMTP_GetPartialObject (G_VFS_BACKEND_MTP (backend)->device, id, offset,
                                       bytes_requested, &temp, &actual);
    if (ret != 0) {
      fail_job (G_VFS_JOB (job), G_VFS_BACKEND_MTP (backend)->device);
      DEBUG ("(I) job failed.\n");
      goto exit;
    }

    memcpy (buffer, temp, actual);
    free (temp);
#else
    g_assert_not_reached ();
#endif
  } else {
    GByteArray *bytes = handle->bytes;
    actual = MIN (bytes->len - offset, bytes_requested);
    memcpy (buffer, bytes->data + offset, actual);
  }

  handle->offset = offset + actual;
  g_vfs_job_read_set_size (job, actual);
  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);
  DEBUG ("(I) do_read done.\n");
}

static void
do_close_read (GVfsBackend *backend,
               GVfsJobCloseRead *job,
               GVfsBackendHandle opaque_handle)
{
  DEBUG ("(I) do_close_read\n");
  RWHandle *handle = opaque_handle;
  if (handle->bytes) {
    g_byte_array_unref (handle->bytes);
  }
  g_free(handle);
  g_vfs_job_succeeded (G_VFS_JOB (job));
  DEBUG ("(I) do_close_read done.\n");
}


#if HAVE_LIBMTP_1_1_6
static uint16_t
zero_get_func (void* params,
               void* priv,
               uint32_t wantlen,
               unsigned char *data,
               uint32_t *gotlen)
{
  *gotlen = 0;
  return LIBMTP_HANDLER_RETURN_OK;
}


static void
do_create (GVfsBackend *backend,
           GVfsJobOpenForWrite *job,
           const char *filename,
           GFileCreateFlags flags)
{
  if (!G_VFS_BACKEND_MTP (backend)->android_extension) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                              _("Operation unsupported"));
    return;
  }

  DEBUG ("(I) do_create (%s)\n", filename);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  char *dir_name = g_path_get_dirname (filename);
  char *basename = g_path_get_basename (filename);

  gchar **elements = g_strsplit_set (filename, "/", -1);
  unsigned int ne = g_strv_length (elements);

  if (ne < 3) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                              _("Cannot write to this location"));
    goto exit;
  }

  CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), dir_name);
  if (!entry) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("Directory doesn't exist"));
    goto exit;
  }

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  LIBMTP_file_t *mtpfile = LIBMTP_new_file_t ();
  mtpfile->filename = strdup (basename);
  mtpfile->parent_id = entry->id;
  mtpfile->storage_id = entry->storage;
  mtpfile->filetype = LIBMTP_FILETYPE_UNKNOWN;
  mtpfile->filesize = 0;

  int ret = LIBMTP_Send_File_From_Handler (device, zero_get_func, NULL,
                                           mtpfile, NULL, NULL);
  uint32_t id = mtpfile->item_id;
  LIBMTP_destroy_file_t (mtpfile);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), device);
    DEBUG ("(I) Failed to create empty file.\n");
    goto exit;
  }

  ret = LIBMTP_BeginEditObject (device, id);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), device);
    DEBUG ("(I) Failed to begin edit.\n");
    goto exit;
  }

  RWHandle *handle = g_new0(RWHandle, 1);
  handle->handle_type = HANDLE_FILE;
  handle->id = id;
  handle->offset = 0;
  handle->size = 0;

  g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
  g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
  g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), handle);
  g_vfs_job_succeeded (G_VFS_JOB (job));

  g_hash_table_foreach (G_VFS_BACKEND_MTP (backend)->monitors,
                        emit_create_event,
                        (char *)filename);
 exit:
  g_strfreev (elements);
  g_free (basename);
  g_free (dir_name);
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_create done.\n");
}


static void
do_append_to (GVfsBackend *backend,
              GVfsJobOpenForWrite *job,
              const char *filename,
              GFileCreateFlags flags)
{
  if (!G_VFS_BACKEND_MTP (backend)->android_extension) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                              _("Operation unsupported"));
    return;
  }

  DEBUG ("(I) do_append_to (%s)\n", filename);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), filename);
  if (entry == NULL) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
                              _("File does not exist"));
    goto exit;
  } else if (entry->id == -1) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                              _("Not a regular file"));
    goto exit;
  }

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, entry->id);
  if (file == NULL) {
    fail_job (G_VFS_JOB (job), device);
    DEBUG ("(I) Failed to get metadata.\n");
    goto exit;
  }

  int ret = LIBMTP_BeginEditObject (device, entry->id);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), device);
    DEBUG ("(I) Failed to begin edit.\n");
    goto exit;
  }

  RWHandle *handle = g_new0(RWHandle, 1);
  handle->handle_type = HANDLE_FILE;
  handle->id = entry->id;
  handle->offset = file->filesize;
  handle->size = file->filesize;

  LIBMTP_destroy_file_t (file);

  g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
  g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
  g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), handle);
  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_append_to done.\n");
}


static void
do_replace (GVfsBackend *backend,
            GVfsJobOpenForWrite *job,
            const char *filename,
            const char *etag,
            gboolean make_backup,
            GFileCreateFlags flags)
{
  if (!G_VFS_BACKEND_MTP (backend)->android_extension) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
                              _("Operation unsupported"));
    return;
  }

  DEBUG ("(I) do_replace (%s)\n", filename);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  CacheEntry *entry = get_cache_entry (G_VFS_BACKEND_MTP (backend), filename);
  if (entry == NULL) {
    g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);
    return do_create(backend, job, filename, flags);
  } else if (entry->id == -1) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
                              _("Not a regular file"));
    goto exit;
  }

  LIBMTP_mtpdevice_t *device;
  device = G_VFS_BACKEND_MTP (backend)->device;

  LIBMTP_file_t *file = LIBMTP_Get_Filemetadata (device, entry->id);
  if (file == NULL) {
    fail_job (G_VFS_JOB (job), device);
    DEBUG ("(I) Failed to get metadata.\n");
    goto exit;
  }

  int ret = LIBMTP_BeginEditObject (device, entry->id);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), device);
    DEBUG ("(I) Failed to begin edit.\n");
    goto exit;
  }

  ret = LIBMTP_TruncateObject (device, entry->id, 0);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), device);
    DEBUG ("(I) Failed to truncate.\n");
    goto exit;
  }

  RWHandle *handle = g_new0(RWHandle, 1);
  handle->handle_type = HANDLE_FILE;
  handle->id = entry->id;
  handle->offset = 0;
  handle->size = file->filesize;

  LIBMTP_destroy_file_t (file);

  g_vfs_job_open_for_write_set_can_seek (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
  g_vfs_job_open_for_write_set_can_truncate (G_VFS_JOB_OPEN_FOR_WRITE (job), TRUE);
  g_vfs_job_open_for_write_set_handle (G_VFS_JOB_OPEN_FOR_WRITE (job), handle);
  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);

  DEBUG ("(I) do_replace done.\n");
}


static void
do_write (GVfsBackend *backend,
          GVfsJobWrite *job,
          GVfsBackendHandle opaque_handle,
          char *buffer,
          gsize buffer_size)
{
  RWHandle *handle = opaque_handle;
  uint32_t id = handle->id;
  goffset offset = handle->offset;

  DEBUG ("(I) do_write (%X %lu %lu)\n", id, offset, buffer_size);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  int ret = LIBMTP_SendPartialObject (G_VFS_BACKEND_MTP (backend)->device, id, offset,
                                      (unsigned char *)buffer, buffer_size);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), G_VFS_BACKEND_MTP (backend)->device);
    DEBUG ("(I) job failed.\n");
    goto exit;
  }

  handle->offset = offset + buffer_size;
  g_vfs_job_write_set_written_size (job, buffer_size);
  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);
  DEBUG ("(I) do_write done.\n");
}


static void
do_seek_on_write (GVfsBackend *backend,
                  GVfsJobSeekWrite *job,
                  GVfsBackendHandle opaque_handle,
                  goffset offset,
                  GSeekType type)
{
  RWHandle *handle = opaque_handle;
  uint32_t id = handle->id;
  goffset old_offset = handle->offset;
  gsize size = handle->size;

  DEBUG ("(I) do_seek_on_write (%X %lu %ld %u)\n", id, old_offset, offset, type);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  if (type == G_SEEK_END) {
    offset = size + offset;
  } else if (type == G_SEEK_CUR) {
    offset += old_offset;
  }

  if (offset < 0) {
    g_vfs_job_failed_literal (G_VFS_JOB (job),
                              G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
                              _("End of stream"));
    goto exit;
  }

  handle->offset = offset;
  g_vfs_job_seek_write_set_offset (job, offset);
  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);
  DEBUG ("(I) do_seek_on_write done. (%lu)\n", offset);
}


static void
do_truncate (GVfsBackend *backend,
             GVfsJobTruncate *job,
             GVfsBackendHandle opaque_handle,
             goffset size)
{
  RWHandle *handle = opaque_handle;
  uint32_t id = handle->id;

  DEBUG("(I) do_truncate (%ld)\n", size);
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  if (LIBMTP_TruncateObject (G_VFS_BACKEND_MTP (backend)->device, id, size) == 0) {
    handle->size = size;
    g_vfs_job_succeeded (G_VFS_JOB (job));
  } else {
    fail_job (G_VFS_JOB (job), G_VFS_BACKEND_MTP (backend)->device);
    DEBUG ("(I) Failed to truncate.\n");
  }

  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);
  DEBUG ("(I) truncate done.\n");
}


static void
do_close_write (GVfsBackend *backend,
                GVfsJobCloseWrite *job,
                GVfsBackendHandle opaque_handle)
{
  DEBUG ("(I) do_close_write\n");
  g_mutex_lock (&G_VFS_BACKEND_MTP (backend)->mutex);

  RWHandle *handle = opaque_handle;

  int ret = LIBMTP_EndEditObject (G_VFS_BACKEND_MTP (backend)->device, handle->id);
  if (ret != 0) {
    fail_job (G_VFS_JOB (job), G_VFS_BACKEND_MTP (backend)->device);
    goto exit;
  }

  g_vfs_job_succeeded (G_VFS_JOB (job));

 exit:
  g_free(handle);
  g_mutex_unlock (&G_VFS_BACKEND_MTP (backend)->mutex);
  DEBUG ("(I) do_close_write done.\n");
}
#endif /* HAVE_LIBMTP_1_1_6 */

/************************************************
 * 	  Class init
 *
 */


static void
g_vfs_backend_mtp_class_init (GVfsBackendMtpClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);

  gobject_class->finalize = g_vfs_backend_mtp_finalize;

  backend_class->mount = do_mount;
  backend_class->unmount = do_unmount;
  backend_class->query_info = do_query_info;
  backend_class->enumerate = do_enumerate;
  backend_class->query_fs_info = do_query_fs_info;
  backend_class->pull = do_pull;
  backend_class->push = do_push;
  backend_class->make_directory = do_make_directory;
  backend_class->delete = do_delete;
  backend_class->set_display_name = do_set_display_name;
  backend_class->create_dir_monitor = do_create_dir_monitor;
  backend_class->create_file_monitor = do_create_file_monitor;
#if HAVE_LIBMTP_1_1_6
  backend_class->open_for_read = do_open_for_read;
#endif
#if HAVE_LIBMTP_1_1_5
  backend_class->open_icon_for_read = do_open_icon_for_read;
#endif
  backend_class->seek_on_read = do_seek_on_read;
  backend_class->read = do_read;
  backend_class->close_read = do_close_read;
#if HAVE_LIBMTP_1_1_6
  backend_class->create = do_create;
  backend_class->append_to = do_append_to;
  backend_class->replace = do_replace;
  backend_class->write = do_write;
  backend_class->seek_on_write = do_seek_on_write;
  backend_class->truncate = do_truncate;
  backend_class->close_write = do_close_write;
#endif
}
