summaryrefslogtreecommitdiff
path: root/daemon
diff options
context:
space:
mode:
Diffstat (limited to 'daemon')
-rw-r--r--daemon/Makefile.am20
-rw-r--r--daemon/google.mount.in4
-rw-r--r--daemon/gvfsbackendgoogle.c2866
-rw-r--r--daemon/gvfsbackendgoogle.h60
4 files changed, 2950 insertions, 0 deletions
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index a970bff6..dbe39165 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -50,6 +50,12 @@ libexec_PROGRAMS=gvfsd gvfsd-sftp gvfsd-trash gvfsd-computer gvfsd-burn gvfsd-lo
mount_in_files = sftp.mount.in ftp.mount.in ftps.mount.in trash.mount.in computer.mount.in burn.mount.in localtest.mount.in network.mount.in
mount_DATA = sftp.mount ftp.mount ftps.mount trash.mount computer.mount burn.mount localtest.mount network.mount
+mount_in_files +=google.mount.in
+if USE_GOOGLE
+mount_DATA += google.mount
+libexec_PROGRAMS += gvfsd-google
+endif
+
mount_in_files +=recent.mount.in
if USE_GTK
mount_DATA += recent.mount
@@ -435,6 +441,20 @@ gvfsd_cdda_LDADD = $(libraries) $(CDDA_LIBS) $(HAL_LIBS) \
$(top_builddir)/common/libgvfscommon-hal.la
endif
+gvfsd_google_SOURCES = \
+ gvfsbackendgoogle.c gvfsbackendgoogle.h \
+ daemon-main.c daemon-main.h \
+ daemon-main-generic.c
+
+gvfsd_google_CPPFLAGS = \
+ $(flags) \
+ -DBACKEND_HEADER=gvfsbackendgoogle.h \
+ -DDEFAULT_BACKEND_TYPE=google-drive \
+ -DBACKEND_TYPES='"google-drive", G_VFS_TYPE_BACKEND_GOOGLE,' \
+ $(GOOGLE_CFLAGS)
+
+gvfsd_google_LDADD = $(libraries) $(GOOGLE_LIBS)
+
gvfsd_gphoto2_SOURCES = \
gvfsbackendgphoto2.c gvfsbackendgphoto2.h \
daemon-main.c daemon-main.h \
diff --git a/daemon/google.mount.in b/daemon/google.mount.in
new file mode 100644
index 00000000..059b2288
--- /dev/null
+++ b/daemon/google.mount.in
@@ -0,0 +1,4 @@
+[Mount]
+Type=google-drive
+Exec=@libexecdir@/gvfsd-google
+AutoMount=false
diff --git a/daemon/gvfsbackendgoogle.c b/daemon/gvfsbackendgoogle.c
new file mode 100644
index 00000000..f0e2b162
--- /dev/null
+++ b/daemon/gvfsbackendgoogle.c
@@ -0,0 +1,2866 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2009 Thibault Saunier
+ * Copyright (C) 2014, 2015 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: Debarshi Ray <debarshir@gnome.org>
+ * Thibault Saunier <saunierthibault@gmail.com>
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+
+#define GOA_API_IS_SUBJECT_TO_CHANGE
+#include <gdata/gdata.h>
+#include <goa/goa.h>
+
+#include "gvfsbackendgoogle.h"
+#include "gvfsicon.h"
+#include "gvfsjobcloseread.h"
+#include "gvfsjobcopy.h"
+#include "gvfsjobcreatemonitor.h"
+#include "gvfsjobenumerate.h"
+#include "gvfsjobopenforread.h"
+#include "gvfsjobopenforwrite.h"
+#include "gvfsjobread.h"
+#include "gvfsjobseekread.h"
+#include "gvfsjobsetdisplayname.h"
+#include "gvfsjobwrite.h"
+#include "gvfsmonitor.h"
+
+struct _GVfsBackendGoogle
+{
+ GVfsBackend parent;
+ GDataDocumentsService *service;
+ GDataEntry *root;
+ GHashTable *entries;
+ GHashTable *dir_entries;
+ GHashTable *monitors;
+ GList *dir_collisions;
+ GRecMutex mutex;
+ GoaClient *client;
+ gboolean entries_stale;
+ gchar *account_identity;
+ guint entries_stale_timeout;
+};
+
+struct _GVfsBackendGoogleClass
+{
+ GVfsBackendClass parent_class;
+};
+
+G_DEFINE_TYPE(GVfsBackendGoogle, g_vfs_backend_google, G_VFS_TYPE_BACKEND)
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+#define CATEGORY_SCHEMA_KIND "http://schemas.google.com/g/2005#kind"
+#define CATEGORY_SCHEMA_KIND_FILE "http://schemas.google.com/docs/2007#file"
+
+#define CONTENT_TYPE_PREFIX_GOOGLE "application/vnd.google-apps"
+
+#define MAX_RESULTS 50
+
+#define REBUILD_ENTRIES_TIMEOUT 60 /* s */
+
+#define URI_PREFIX "https://www.googleapis.com/drive/v2/files/"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+typedef struct
+{
+ gchar *title_or_id;
+ gchar *parent_id;
+} DirEntriesKey;
+
+typedef struct
+{
+ GDataEntry *document;
+ GDataUploadStream *stream;
+ gchar *filename;
+} WriteHandle;
+
+static GDataEntry *resolve_dir (GVfsBackendGoogle *self,
+ const gchar *filename,
+ gchar **out_basename,
+ GError **error);
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+DirEntriesKey *
+dir_entries_key_new (const gchar *title_or_id, const gchar *parent_id)
+{
+ DirEntriesKey *k;
+
+ k = g_slice_new0 (DirEntriesKey);
+ k->title_or_id = g_strdup (title_or_id);
+ k->parent_id = g_strdup (parent_id);
+ return k;
+}
+
+static void
+dir_entries_key_free (gpointer data)
+{
+ DirEntriesKey *k = (DirEntriesKey *) data;
+
+ if (k == NULL)
+ return;
+
+ g_free (k->title_or_id);
+ g_free (k->parent_id);
+ g_slice_free (DirEntriesKey, k);
+}
+
+guint
+entries_in_folder_hash (gconstpointer key)
+{
+ DirEntriesKey *k = (DirEntriesKey *) key;
+ guint hash1;
+ guint hash2;
+
+ hash1 = g_str_hash (k->title_or_id);
+ hash2 = g_str_hash (k->parent_id);
+ return hash1 ^ hash2;
+}
+
+gboolean
+entries_in_folder_equal (gconstpointer a, gconstpointer b)
+{
+ DirEntriesKey *k_a = (DirEntriesKey *) a;
+ DirEntriesKey *k_b = (DirEntriesKey *) b;
+
+ if (g_strcmp0 (k_a->title_or_id, k_b->title_or_id) == 0 &&
+ g_strcmp0 (k_a->parent_id, k_b->parent_id) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+WriteHandle *
+write_handle_new (GDataEntry *document, GDataUploadStream *stream, const gchar *filename)
+{
+ WriteHandle *handle;
+
+ handle = g_slice_new0 (WriteHandle);
+
+ if (document != NULL)
+ handle->document = g_object_ref (document);
+
+ if (stream != NULL)
+ {
+ handle->stream = g_object_ref (stream);
+ if (handle->document == NULL)
+ handle->document = g_object_ref (gdata_upload_stream_get_entry (stream));
+ }
+
+ handle->filename = g_strdup (filename);
+
+ return handle;
+}
+
+static void
+write_handle_free (gpointer data)
+{
+ WriteHandle *handle = (WriteHandle *) data;
+
+ if (handle == NULL)
+ return;
+
+ g_clear_object (&handle->document);
+ g_clear_object (&handle->stream);
+ g_free (handle->filename);
+ g_slice_free (WriteHandle, handle);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+sanitize_error (GError **error)
+{
+ if (g_error_matches (*error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED) ||
+ g_error_matches (*error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_FORBIDDEN))
+ {
+ g_warning ("%s", (*error)->message);
+ g_clear_error (error);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Permission denied"));
+ }
+ else if (g_error_matches (*error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND))
+ {
+ g_warning ("%s", (*error)->message);
+ g_clear_error (error);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Target object doesn't exist"));
+ }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+emit_event_internal (GVfsMonitor *monitor,
+ const gchar *entry_path,
+ GFileMonitorEvent event)
+{
+ const gchar *monitored_path;
+ gchar *parent_path;
+
+ if (entry_path == NULL)
+ return;
+
+ monitored_path = g_object_get_data (G_OBJECT (monitor), "g-vfs-backend-google-path");
+ parent_path = g_path_get_dirname (entry_path);
+
+ if (g_strcmp0 (parent_path, monitored_path) == 0)
+ {
+ g_debug (" emit event %d on parent directory for %s\n", event, entry_path);
+ g_vfs_monitor_emit_event (monitor, event, entry_path, NULL);
+ }
+ else if (g_strcmp0 (entry_path, monitored_path) == 0)
+ {
+ g_debug (" emit event %d on file %s\n", event, entry_path);
+ g_vfs_monitor_emit_event (monitor, event, entry_path, NULL);
+ }
+
+ g_free (parent_path);
+}
+
+static void
+emit_attribute_changed_event (gpointer monitor,
+ gpointer unused,
+ gpointer entry_path)
+{
+ emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED);
+}
+
+static void
+emit_changed_event (gpointer monitor,
+ gpointer unused,
+ gpointer entry_path)
+{
+ emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_CHANGED);
+}
+
+static void
+emit_changes_done_event (gpointer monitor,
+ gpointer unused,
+ gpointer entry_path)
+{
+ emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT);
+}
+
+static void
+emit_create_event (gpointer monitor,
+ gpointer unused,
+ gpointer entry_path)
+{
+ emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_CREATED);
+}
+
+static void
+emit_delete_event (gpointer monitor,
+ gpointer unused,
+ gpointer entry_path)
+{
+ emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_DELETED);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+get_content_type_from_entry (GDataEntry *entry)
+{
+ GList *categories;
+ GList *l;
+ gchar *ret_val = NULL;
+
+ categories = gdata_entry_get_categories (entry);
+ for (l = categories; l != NULL; l = l->next)
+ {
+ GDataCategory *category = GDATA_CATEGORY (l->data);
+ const gchar *scheme;
+
+ scheme = gdata_category_get_scheme (category);
+ if (g_strcmp0 (scheme, CATEGORY_SCHEMA_KIND) == 0)
+ {
+ ret_val = g_strdup (gdata_category_get_label (category));
+ break;
+ }
+ }
+
+ return ret_val;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+get_parent_id (GVfsBackendGoogle *self,
+ GDataEntry *entry)
+{
+ GList *l;
+ GList *links = NULL;
+ gchar *ret_val = NULL;
+
+ links = gdata_entry_look_up_links (entry, GDATA_LINK_PARENT);
+ for (l = links; l != NULL; l = l->next)
+ {
+ GDataLink *link = GDATA_LINK (l->data);
+ const gchar *uri;
+
+ /* HACK: GDataLink does not have the ID, only the URI. Extract
+ * the ID from the GDataLink:uri by removing the prefix. Ignore
+ * links which don't have the prefix.
+ */
+ uri = gdata_link_get_uri (link);
+ if (g_str_has_prefix (uri, URI_PREFIX))
+ {
+ const gchar *id;
+ gsize uri_prefix_len;
+
+ uri_prefix_len = strlen (URI_PREFIX);
+ id = uri + uri_prefix_len;
+ if (id[0] != '\0')
+ {
+ ret_val = g_strdup (uri + uri_prefix_len);
+ break;
+ }
+ }
+ }
+
+ if (ret_val == NULL)
+ {
+ const gchar *root_id;
+
+ root_id = gdata_entry_get_id (self->root);
+ ret_val = g_strdup (root_id);
+ }
+
+ g_list_free (links);
+ return ret_val;
+}
+
+static gchar *
+get_entry_path (GVfsBackendGoogle *self, GDataEntry *entry)
+{
+ GString *path = NULL;
+ const gchar *base_id;
+ const gchar *root_id;
+ gchar *id = NULL;
+ gchar *ret_val = NULL;
+
+ if (entry == self->root)
+ {
+ ret_val = g_strdup ("/");
+ goto out;
+ }
+
+ base_id = gdata_entry_get_id (entry);
+ path = g_string_new (base_id);
+ g_string_prepend_c (path, '/');
+
+ id = get_parent_id (self, entry);
+ root_id = gdata_entry_get_id (self->root);
+
+ while (id != NULL)
+ {
+ GDataEntry *parent_entry;
+
+ /* The root folder itself has an ID, so path can become
+ * /root/folder1/folder2/file. Instead, we want it to be
+ * /folder1/folder2/file.
+ */
+
+ if (g_strcmp0 (id, root_id) == 0)
+ break;
+
+ parent_entry = g_hash_table_lookup (self->entries, id);
+ if (parent_entry == NULL)
+ goto out;
+
+ g_string_prepend (path, id);
+ g_string_prepend_c (path, '/');
+
+ g_free (id);
+ id = get_parent_id (self, parent_entry);
+ }
+
+ ret_val = g_strdup (path->str);
+
+ out:
+ g_free (id);
+ if (path != NULL)
+ g_string_free (path, TRUE);
+ return ret_val;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+insert_entry_full (GVfsBackendGoogle *self,
+ GDataEntry *entry,
+ gboolean track_dir_collisions)
+{
+ DirEntriesKey *k;
+ GDataEntry *old_entry;
+ gboolean insert_title = TRUE;
+ const gchar *id;
+ const gchar *old_id;
+ const gchar *title;
+ gchar *parent_id;
+
+ id = gdata_entry_get_id (entry);
+ title = gdata_entry_get_title (entry);
+
+ g_hash_table_insert (self->entries, g_strdup (id), g_object_ref (entry));
+
+ parent_id = get_parent_id (self, entry);
+
+ k = dir_entries_key_new (id, parent_id);
+ g_hash_table_insert (self->dir_entries, k, g_object_ref (entry));
+ g_debug (" insert_entry: Inserted (%s, %s) -> %p\n", id, parent_id, entry);
+
+ k = dir_entries_key_new (title, parent_id);
+ old_entry = g_hash_table_lookup (self->dir_entries, k);
+ if (old_entry != NULL)
+ {
+ old_id = gdata_entry_get_id (old_entry);
+ if (g_strcmp0 (old_id, title) == 0)
+ {
+ insert_title = FALSE;
+ }
+ else
+ {
+ /* If the collision is not due to the title matching the ID
+ * of an earlier GDataEntry, then it is due to duplicate
+ * titles.
+ */
+ if (g_strcmp0 (old_id, id) < 0)
+ insert_title = FALSE;
+ }
+ }
+
+ if (insert_title)
+ {
+ if (old_entry != NULL && track_dir_collisions)
+ {
+ self->dir_collisions = g_list_prepend (self->dir_collisions, g_object_ref (old_entry));
+ g_debug (" insert_entry: Ejected (%s, %s, %s) -> %p\n", old_id, title, parent_id, old_entry);
+ }
+
+ g_hash_table_insert (self->dir_entries, k, g_object_ref (entry));
+ g_debug (" insert_entry: Inserted (%s, %s) -> %p\n", title, parent_id, entry);
+ }
+ else
+ {
+ if (track_dir_collisions)
+ {
+ self->dir_collisions = g_list_prepend (self->dir_collisions, g_object_ref (entry));
+ g_debug (" insert_entry: Skipped (%s, %s, %s) -> %p\n", id, title, parent_id, entry);
+ }
+
+ dir_entries_key_free (k);
+ }
+
+ g_free (parent_id);
+ return insert_title;
+}
+
+static void
+insert_entry (GVfsBackendGoogle *self,
+ GDataEntry *entry)
+{
+ insert_entry_full (self, entry, TRUE);
+}
+
+static void
+remove_entry (GVfsBackendGoogle *self,
+ GDataEntry *entry)
+{
+ DirEntriesKey *k;
+ GList *l;
+ const gchar *id;
+ const gchar *title;
+ gchar *parent_id;
+
+ id = gdata_entry_get_id (entry);
+ title = gdata_entry_get_title (entry);
+
+ g_hash_table_remove (self->entries, id);
+
+ parent_id = get_parent_id (self, entry);
+
+ k = dir_entries_key_new (id, parent_id);
+ g_hash_table_remove (self->dir_entries, k);
+ g_debug (" remove_entry: Removed (%s, %s) -> %p\n", id, parent_id, entry);
+ dir_entries_key_free (k);
+
+ k = dir_entries_key_new (title, parent_id);
+ g_hash_table_remove (self->dir_entries, k);
+ g_debug (" remove_entry: Removed (%s, %s) -> %p\n", title, parent_id, entry);
+ dir_entries_key_free (k);
+
+ for (l = self->dir_collisions; l != NULL; l = l->next)
+ {
+ GDataEntry *colliding_entry = GDATA_ENTRY (l->data);
+
+ if (insert_entry_full (self, colliding_entry, FALSE))
+ {
+ self->dir_collisions = g_list_remove_link (self->dir_collisions, l);
+ g_debug (" remove_entry: Restored %p\n", colliding_entry);
+ g_list_free (l);
+ g_object_unref (colliding_entry);
+ break;
+ }
+ }
+
+ g_free (parent_id);
+}
+
+static void
+rebuild_entries (GVfsBackendGoogle *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDataDocumentsFeed *feed = NULL;
+ GDataDocumentsQuery *query = NULL;
+ gboolean succeeded_once = FALSE;
+
+ query = gdata_documents_query_new_with_limits (NULL, 1, MAX_RESULTS);
+ gdata_documents_query_set_show_folders (query, TRUE);
+
+ while (TRUE)
+ {
+ GError *local_error;
+ GList *entries;
+ GList *l;
+
+ local_error = NULL;
+ feed = gdata_documents_service_query_documents (self->service, query, cancellable, NULL, NULL, &local_error);
+ if (local_error != NULL)
+ {
+ if (succeeded_once)
+ {
+ g_warning ("Unable to query: %s", local_error->message);
+ g_error_free (local_error);
+ }
+ else
+ {
+ sanitize_error (&local_error);
+ g_propagate_error (error, local_error);
+ }
+
+ break;
+ }
+
+ if (!succeeded_once)
+ {
+ g_hash_table_remove_all (self->entries);
+ g_hash_table_remove_all (self->dir_entries);
+
+ g_list_free_full (self->dir_collisions, g_object_unref);
+ self->dir_collisions = NULL;
+
+ succeeded_once = TRUE;
+ }
+
+ entries = gdata_feed_get_entries (GDATA_FEED (feed));
+ if (entries == NULL)
+ break;
+
+ for (l = entries; l != NULL; l = l->next)
+ {
+ GDataEntry *entry = GDATA_ENTRY (l->data);
+ insert_entry (self, entry);
+ }
+
+ gdata_query_next_page (GDATA_QUERY (query));
+ g_clear_object (&feed);
+ }
+
+ self->entries_stale = FALSE;
+
+ g_clear_object (&feed);
+ g_clear_object (&query);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDataEntry *
+resolve_child (GVfsBackendGoogle *self,
+ GDataEntry *parent,
+ const gchar *basename)
+{
+ DirEntriesKey *k;
+ GDataEntry *entry;
+ const gchar *parent_id;
+
+ parent_id = gdata_entry_get_id (parent);
+ k = dir_entries_key_new (basename, parent_id);
+ entry = g_hash_table_lookup (self->dir_entries, k);
+ dir_entries_key_free (k);
+ return entry;
+}
+
+static GDataEntry *
+resolve (GVfsBackendGoogle *self,
+ const gchar *filename,
+ GError **error)
+{
+ GDataEntry *parent;
+ GDataEntry *ret_val = NULL;
+ GError *local_error;
+ gchar *basename = NULL;
+
+ if (g_strcmp0 (filename, "/") == 0)
+ {
+ ret_val = self->root;
+ goto out;
+ }
+
+ local_error = NULL;
+ parent = resolve_dir (self, filename, &basename, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+
+ ret_val = resolve_child (self, parent, basename);
+ if (ret_val == NULL)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or directory"));
+ goto out;
+ }
+
+ out:
+ g_free (basename);
+ return ret_val;
+}
+
+static GDataEntry *
+resolve_dir (GVfsBackendGoogle *self,
+ const gchar *filename,
+ gchar **out_basename,
+ GError **error)
+{
+ GDataEntry *parent;
+ GDataEntry *ret_val = NULL;
+ GError *local_error;
+ gchar *basename = NULL;
+ gchar *parent_path = NULL;
+
+ basename = g_path_get_basename (filename);
+ parent_path = g_path_get_dirname (filename);
+
+ local_error = NULL;
+ parent = resolve (self, parent_path, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+
+ if (!GDATA_IS_DOCUMENTS_FOLDER (parent))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, _("The file is not a directory"));
+ goto out;
+ }
+
+ if (out_basename != NULL)
+ {
+ *out_basename = basename;
+ basename = NULL;
+ }
+
+ ret_val = parent;
+
+ out:
+ g_free (basename);
+ g_free (parent_path);
+ return ret_val;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GDataEntry *
+resolve_and_rebuild (GVfsBackendGoogle *self,
+ const gchar *filename,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDataEntry *entry;
+ GDataEntry *ret_val = NULL;
+
+ entry = resolve (self, filename, NULL);
+ if (entry == NULL)
+ {
+ GError *local_error;
+
+ local_error = NULL;
+ rebuild_entries (self, cancellable, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+
+ local_error = NULL;
+ entry = resolve (self, filename, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+ }
+
+ ret_val = entry;
+
+ out:
+ return ret_val;
+}
+
+static GDataEntry *
+resolve_dir_and_rebuild (GVfsBackendGoogle *self,
+ const gchar *filename,
+ GCancellable *cancellable,
+ gchar **out_basename,
+ GError **error)
+{
+ GDataEntry *parent;
+ GDataEntry *ret_val = NULL;
+ GError *local_error;
+ gchar *basename;
+
+ local_error = NULL;
+ parent = resolve_dir (self, filename, &basename, &local_error);
+ if (local_error != NULL)
+ {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY))
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+ else
+ {
+ g_error_free (local_error);
+ }
+
+ local_error = NULL;
+ rebuild_entries (self, cancellable, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+
+ local_error = NULL;
+ parent = resolve_dir (self, filename, &basename, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+ }
+
+ if (out_basename != NULL)
+ {
+ *out_basename = basename;
+ basename = NULL;
+ }
+
+ ret_val = parent;
+
+ out:
+ g_free (basename);
+ return ret_val;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static char *
+get_extension_offset (const char *title)
+{
+ gchar *end;
+ gchar *end2;
+
+ end = strrchr (title, '.');
+
+ if (end != NULL && end != title)
+ {
+ if (g_strcmp0 (end, ".gz") == 0 ||
+ g_strcmp0 (end, ".bz2") == 0 ||
+ g_strcmp0 (end, ".sit") == 0 ||
+ g_strcmp0 (end, ".zip") == 0 ||
+ g_strcmp0 (end, ".Z") == 0)
+ {
+ end2 = end - 1;
+ while (end2 > title && *end2 != '.')
+ end2--;
+
+ if (end2 != title)
+ end = end2;
+ }
+ }
+
+ return end;
+}
+
+static gchar *
+generate_copy_name (GVfsBackendGoogle *self, GDataEntry *entry)
+{
+ GDataEntry *existing_entry;
+ GDataEntry *parent;
+ const gchar *id;
+ const gchar *title;
+ gchar *entry_path = NULL;
+ gchar *extension = NULL;
+ gchar *extension_offset;
+ gchar *ret_val = NULL;
+ gchar *title_without_extension = NULL;
+
+ title = gdata_entry_get_title (entry);
+
+ entry_path = get_entry_path (self, entry);
+ if (entry_path == NULL)
+ goto out;
+
+ parent = resolve_dir (self, entry_path, NULL, NULL);
+ if (parent == NULL)
+ goto out;
+
+ existing_entry = resolve_child (self, parent, title);
+ if (existing_entry == entry)
+ goto out;
+
+ title_without_extension = g_strdup (title);
+ extension_offset = get_extension_offset (title_without_extension);
+ if (extension_offset != NULL && extension_offset != title_without_extension)
+ {
+ extension = g_strdup (extension_offset);
+ *extension_offset = '\0';
+ }
+
+ id = gdata_entry_get_id (entry);
+ ret_val = g_strdup_printf ("%s - %s%s", title_without_extension, id, (extension == NULL) ? "" : extension);
+
+ out:
+ if (ret_val == NULL)
+ ret_val = g_strdup (title);
+ g_free (entry_path);
+ g_free (extension);
+ g_free (title_without_extension);
+ return ret_val;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+build_file_info (GVfsBackendGoogle *self,
+ GDataEntry *entry,
+ GFileQueryInfoFlags flags,
+ GFileInfo *info,
+ GFileAttributeMatcher *matcher,
+ gboolean is_symlink,
+ const gchar *symlink_name,
+ const gchar *symlink_target,
+ GError **error)
+{
+ GFileType file_type;
+ GList *authors;
+ gboolean is_folder = FALSE;
+ gboolean is_root = FALSE;
+ const gchar *etag;
+ const gchar *id;
+ const gchar *name;
+ const gchar *title;
+ gchar *escaped_name = NULL;
+ gchar *content_type = NULL;
+ gchar *copy_name = NULL;
+ gchar *generated_copy_name = NULL;
+ gint64 atime;
+ gint64 ctime;
+ gint64 mtime;
+ gsize i;
+
+ if (GDATA_IS_DOCUMENTS_FOLDER (entry))
+ is_folder = TRUE;
+
+ if (entry == self->root)
+ is_root = TRUE;
+
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, !is_root);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME, !is_root);
+
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, is_folder);
+
+ g_file_info_set_is_symlink (info, is_symlink);
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE, is_symlink);
+
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
+
+ if (is_folder)
+ {
+ content_type = g_strdup ("inode/directory");
+ file_type = G_FILE_TYPE_DIRECTORY;
+ }
+ else
+ {
+ content_type = get_content_type_from_entry (entry);
+ file_type = G_FILE_TYPE_REGULAR;
+
+ /* We want native Drive content to open in the browser. */
+ if (content_type != NULL && g_str_has_prefix (content_type, CONTENT_TYPE_PREFIX_GOOGLE))
+ {
+ GDataLink *alternate;
+ const gchar *uri;
+
+ file_type = G_FILE_TYPE_SHORTCUT;
+ alternate = gdata_entry_look_up_link (entry, GDATA_LINK_ALTERNATE);
+ uri = gdata_link_get_uri (alternate);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, uri);
+ }
+ else
+ {
+ goffset quota_used;
+
+ quota_used = gdata_documents_entry_get_quota_used (GDATA_DOCUMENTS_ENTRY (entry));
+ g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE, (guint64) quota_used);
+ }
+ }
+
+ if (is_symlink)
+ {
+ if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
+ {
+ g_free (content_type);
+ content_type = g_strdup ("inode/symlink");
+ file_type = G_FILE_TYPE_SYMBOLIC_LINK;
+ }
+
+ g_file_info_set_symlink_target (info, symlink_target);
+ }
+
+ if (content_type != NULL)
+ {
+ GIcon *icon;
+ GIcon *symbolic_icon;
+
+ g_file_info_set_content_type (info, content_type);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, content_type);
+
+ if (file_type == G_FILE_TYPE_DIRECTORY)
+ {
+ icon = g_themed_icon_new ("folder");
+ symbolic_icon = g_themed_icon_new ("folder-symbolic");
+ }
+ else
+ {
+ icon = g_content_type_get_icon (content_type);
+ symbolic_icon = g_content_type_get_symbolic_icon (content_type);
+ }
+
+ g_file_info_set_icon (info, icon);
+ g_file_info_set_symbolic_icon (info, symbolic_icon);
+
+ g_object_unref (icon);
+ g_object_unref (symbolic_icon);
+ }
+
+ g_file_info_set_file_type (info, file_type);
+
+ if (is_root)
+ goto out;
+
+ id = gdata_entry_get_id (entry);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE, id);
+
+ if (is_symlink)
+ name = symlink_name;
+ else
+ name = id;
+
+ g_file_info_set_name (info, name);
+
+ title = gdata_entry_get_title (entry);
+ g_file_info_set_display_name (info, title);
+ g_file_info_set_edit_name (info, title);
+
+ generated_copy_name = generate_copy_name (self, entry);
+
+ /* While copying remote Drive content to local storage, we want to
+ * create Link-type desktop files because GLocalFile doesn't know
+ * about shortcuts. That might change in future.
+ */
+ if (file_type == G_FILE_TYPE_SHORTCUT)
+ {
+ copy_name = g_strconcat (generated_copy_name, ".desktop", NULL);
+ }
+ else
+ {
+ copy_name = generated_copy_name;
+ generated_copy_name = NULL;
+ }
+
+ /* Sanitize copy-name by replacing slashes with dashes. This is
+ * what nautilus does (for desktop files).
+ */
+ for (i = 0; copy_name[i] != '\0'; i++)
+ {
+ if (copy_name[i] == '/')
+ copy_name[i] = '-';
+ }
+
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, copy_name);
+
+ atime = gdata_documents_entry_get_last_viewed (GDATA_DOCUMENTS_ENTRY (entry));
+ g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, (guint64) atime);
+
+ ctime = gdata_entry_get_published (entry);
+ g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CREATED, (guint64) ctime);
+
+ mtime = gdata_entry_get_updated (entry);
+ g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, (guint64) mtime);
+
+ authors = gdata_entry_get_authors (entry);
+ if (authors != NULL)
+ {
+ GDataAuthor *author = GDATA_AUTHOR (authors->data);
+ const gchar *author_name;
+ const gchar *email_address;
+
+ author_name = gdata_author_get_name (author);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER_REAL, author_name);
+
+ email_address = gdata_author_get_email_address (author);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER, email_address);
+ }
+
+ etag = gdata_entry_get_etag (entry);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
+
+ if (!is_folder)
+ {
+ const gchar *thumbnail_uri;
+
+ thumbnail_uri = gdata_documents_document_get_thumbnail_uri (GDATA_DOCUMENTS_DOCUMENT (entry));
+ if (thumbnail_uri != NULL && thumbnail_uri[0] != '\0')
+ {
+ GIcon *preview;
+ GMountSpec *spec;
+
+ spec = g_vfs_backend_get_mount_spec (G_VFS_BACKEND (self));
+ preview = g_vfs_icon_new (spec, thumbnail_uri);
+ g_file_info_set_attribute_object (info, G_FILE_ATTRIBUTE_PREVIEW_ICON, G_OBJECT (preview));
+ g_object_unref (preview);
+ }
+ }
+
+ out:
+ g_free (copy_name);
+ g_free (generated_copy_name);
+ g_free (escaped_name);
+ g_free (content_type);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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_google_copy (GVfsBackend *_self,
+ GVfsJobCopy *job,
+ const gchar *source,
+ const gchar *destination,
+ GFileCopyFlags flags,
+ GFileProgressCallback progress_callback,
+ gpointer progress_callback_data)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataDocumentsEntry *dummy_source_entry = NULL;
+ GDataDocumentsEntry *new_entry = NULL;
+ GDataEntry *destination_parent;
+ GDataEntry *existing_entry;
+ GDataEntry *source_entry;
+ GError *error;
+ GType source_entry_type;
+ gboolean needs_rebuild = FALSE;
+ gboolean destination_not_directory = FALSE;
+ const gchar *etag;
+ const gchar *id;
+ const gchar *summary;
+ gchar *destination_basename = NULL;
+ gchar *entry_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ copy: %s -> %s, %d\n", source, destination, flags);
+
+ if (flags & G_FILE_COPY_BACKUP)
+ {
+ /* Return G_IO_ERROR_NOT_SUPPORTED instead of
+ * G_IO_ERROR_CANT_CREATE_BACKUP to proceed with the GIO
+ * fallback copy.
+ */
+ g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation unsupported"));
+ goto out;
+ }
+
+ source_entry = resolve (self, source, NULL);
+ if (source_entry == NULL)
+ needs_rebuild = TRUE;
+
+ error = NULL;
+ destination_parent = resolve_dir (self, destination, &destination_basename, &error);
+ if (error != NULL)
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY))
+ destination_not_directory = TRUE;
+ else
+ needs_rebuild = TRUE;
+
+ g_error_free (error);
+ }
+
+ if (needs_rebuild)
+ {
+ error = NULL;
+ rebuild_entries (self, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ error = NULL;
+ source_entry = resolve (self, source, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ if (!destination_not_directory)
+ {
+ g_free (destination_basename);
+
+ error = NULL;
+ destination_parent = resolve_dir (self, destination, &destination_basename, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+ }
+ }
+
+ if (destination_not_directory)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, _("The file is not a directory"));
+ goto out;
+ }
+
+ existing_entry = resolve_child (self, destination_parent, destination_basename);
+ if (existing_entry != NULL)
+ {
+ if (flags & G_FILE_COPY_OVERWRITE)
+ {
+ /* We don't support overwrites, so we don't need to care
+ * about G_IO_ERROR_IS_DIRECTORY and G_IO_ERROR_WOULD_MERGE.
+ */
+ g_vfs_job_failed_literal (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Operation unsupported"));
+ goto out;
+ }
+ else
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
+ goto out;
+ }
+ }
+ else
+ {
+ if (GDATA_IS_DOCUMENTS_FOLDER (source_entry))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_WOULD_RECURSE,
+ _("Can't recursively copy directory"));
+ goto out;
+ }
+ }
+
+ etag = gdata_entry_get_etag (source_entry);
+ id = gdata_entry_get_id (source_entry);
+ summary = gdata_entry_get_summary (source_entry);
+
+ source_entry_type = G_OBJECT_TYPE (source_entry);
+ dummy_source_entry = g_object_new (source_entry_type,
+ "etag", etag,
+ "id", id,
+ "summary", summary,
+ "title", destination_basename,
+ NULL);
+
+ error = NULL;
+ new_entry = gdata_documents_service_add_entry_to_folder (self->service,
+ dummy_source_entry,
+ GDATA_DOCUMENTS_FOLDER (destination_parent),
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, GDATA_ENTRY (new_entry));
+ g_debug (" new entry path: %s\n", entry_path);
+
+ insert_entry (self, GDATA_ENTRY (new_entry));
+ g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&dummy_source_entry);
+ g_clear_object (&new_entry);
+ g_free (destination_basename);
+ g_free (entry_path);
+ g_debug ("- copy\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+g_vfs_backend_google_create_dir_monitor (GVfsBackend *_self,
+ GVfsJobCreateMonitor *job,
+ const gchar *filename,
+ GFileMonitorFlags flags)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataEntry *entry;
+ GError *error;
+ GVfsMonitor *monitor = NULL;
+ gchar *entry_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ create_dir_monitor: %s, %d\n", filename, flags);
+
+ if (flags & G_FILE_MONITOR_SEND_MOVED)
+ {
+ g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation unsupported"));
+ goto out;
+ }
+
+ error = NULL;
+ entry = resolve_and_rebuild (self, filename, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, entry);
+ g_debug (" entry path: %s\n", entry_path);
+
+ if (!GDATA_IS_DOCUMENTS_FOLDER (entry))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, _("The file is not a directory"));
+ goto out;
+ }
+
+ monitor = g_vfs_monitor_new (_self);
+ g_object_set_data_full (G_OBJECT (monitor), "g-vfs-backend-google-path", g_strdup (entry_path), g_free);
+ g_hash_table_add (self->monitors, monitor);
+ g_object_weak_ref (G_OBJECT (monitor), (GWeakNotify) g_hash_table_remove, self->monitors);
+ g_vfs_job_create_monitor_set_monitor (job, monitor);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&monitor);
+ g_free (entry_path);
+ g_debug ("- create_dir_monitor\n");
+ g_rec_mutex_unlock (&self->mutex);
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_delete (GVfsBackend *_self,
+ GVfsJobDelete *job,
+ const gchar *filename)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataAuthorizationDomain *auth_domain;
+ GDataEntry *entry;
+ GError *error;
+ gchar *entry_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ delete: %s\n", filename);
+
+ error = NULL;
+ entry = resolve_and_rebuild (self, filename, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, entry);
+ g_debug (" entry path: %s\n", entry_path);
+
+ if (entry == self->root)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation unsupported"));
+ goto out;
+ }
+
+ auth_domain = gdata_documents_service_get_primary_authorization_domain ();
+
+ error = NULL;
+ gdata_service_delete_entry (GDATA_SERVICE (self->service), auth_domain, entry, cancellable, &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ remove_entry (self, entry);
+ g_hash_table_foreach (self->monitors, emit_delete_event, entry_path);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (entry_path);
+ g_debug ("- delete\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+rebuild_entries_timeout_cb (gpointer user_data)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (user_data);
+
+ self->entries_stale = TRUE;
+ self->entries_stale_timeout = 0;
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+g_vfs_backend_google_enumerate (GVfsBackend *_self,
+ GVfsJobEnumerate *job,
+ const gchar *filename,
+ GFileAttributeMatcher *matcher,
+ GFileQueryInfoFlags flags)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataEntry *entry;
+ GError *error;
+ GHashTableIter iter;
+ gchar *entry_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ enumerate: %s\n", filename);
+
+ if (self->entries_stale_timeout == 0)
+ {
+ self->entries_stale_timeout = g_timeout_add_seconds_full (G_PRIORITY_DEFAULT,
+ REBUILD_ENTRIES_TIMEOUT,
+ rebuild_entries_timeout_cb,
+ g_object_ref (self),
+ g_object_unref);
+ }
+
+ if (self->entries_stale)
+ {
+ error = NULL;
+ rebuild_entries (self, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+ }
+
+ error = NULL;
+ entry = resolve (self, filename, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, entry);
+ g_debug (" entry path: %s\n", entry_path);
+
+ if (!GDATA_IS_DOCUMENTS_FOLDER (entry))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,_("The file is not a directory"));
+ goto out;
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ g_hash_table_iter_init (&iter, self->entries);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &entry))
+ {
+ gchar *path;
+
+ path = get_entry_path (self, entry);
+ g_debug (" found entry: %s\n", path);
+ if (path != NULL)
+ {
+ gchar *parent_path;
+
+ parent_path = g_path_get_dirname (path);
+ if (g_strcmp0 (entry_path, parent_path) == 0)
+ {
+ GFileInfo *info;
+
+ info = g_file_info_new ();
+ build_file_info (self, entry, flags, info, matcher, FALSE, NULL, NULL, NULL);
+ g_vfs_job_enumerate_add_info (job, info);
+ g_object_unref (info);
+ }
+
+ g_free (parent_path);
+ }
+
+ g_free (path);
+ }
+
+ g_vfs_job_enumerate_done (job);
+
+ out:
+ g_free (entry_path);
+ g_debug ("- enumerate\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_make_directory (GVfsBackend *_self,
+ GVfsJobMakeDirectory *job,
+ const gchar *filename)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataDocumentsEntry *new_folder = NULL;
+ GDataDocumentsFolder *folder = NULL;
+ GDataEntry *existing_entry;
+ GDataEntry *parent;
+ GDataEntry *summary_entry;
+ GError *error;
+ const gchar *summary;
+ gchar *entry_path = NULL;
+ gchar *basename = NULL;
+ gchar *parent_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ make_directory: %s\n", filename);
+
+ if (g_strcmp0 (filename, "/") == 0)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation unsupported"));
+ goto out;
+ }
+
+ error = NULL;
+ parent = resolve_dir_and_rebuild (self, filename, cancellable, &basename, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ parent_path = get_entry_path (self, parent);
+ g_debug (" parent path: %s\n", parent_path);
+
+ summary_entry = g_hash_table_lookup (self->entries, basename);
+ if (summary_entry == NULL)
+ summary = NULL;
+ else
+ summary = gdata_entry_get_summary (summary_entry);
+
+ existing_entry = resolve_child (self, parent, basename);
+ if (existing_entry != NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
+ goto out;
+ }
+
+ folder = gdata_documents_folder_new (NULL);
+ gdata_entry_set_title (GDATA_ENTRY (folder), basename);
+ gdata_entry_set_summary (GDATA_ENTRY (folder), summary);
+
+ error = NULL;
+ new_folder = gdata_documents_service_add_entry_to_folder (self->service,
+ GDATA_DOCUMENTS_ENTRY (folder),
+ GDATA_DOCUMENTS_FOLDER (parent),
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, GDATA_ENTRY (new_folder));
+ g_debug (" new entry path: %s\n", entry_path);
+
+ insert_entry (self, GDATA_ENTRY (new_folder));
+ g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&folder);
+ g_clear_object (&new_folder);
+ g_free (basename);
+ g_free (entry_path);
+ g_free (parent_path);
+ g_debug ("- make_directory\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_mount (GVfsBackend *_self,
+ GVfsJobMount *job,
+ GMountSpec *spec,
+ GMountSource *source,
+ gboolean is_automount)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataAuthorizationDomain *auth_domain;
+ GError *error;
+ GList *accounts = NULL;
+ GList *l;
+ gboolean account_found = FALSE;
+ const gchar *host;
+ const gchar *user;
+
+ g_debug ("+ mount\n");
+
+ error = NULL;
+ self->client = goa_client_new_sync (cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ host = g_mount_spec_get (spec, "host");
+ user = g_mount_spec_get (spec, "user");
+ self->account_identity = g_strconcat (user, "@", host, NULL);
+
+ accounts = goa_client_get_accounts (self->client);
+ for (l = accounts; l != NULL && !account_found; l = l->next)
+ {
+ GoaAccount *account;
+ GoaObject *object = GOA_OBJECT (l->data);
+ gchar *account_identity;
+ gchar *provider_type;
+
+ account = goa_object_get_account (object);
+ account_identity = goa_account_dup_identity (account);
+ provider_type = goa_account_dup_provider_type (account);
+
+ if (g_strcmp0 (provider_type, "google") == 0 &&
+ g_strcmp0 (account_identity, self->account_identity) == 0)
+ {
+ GDataGoaAuthorizer *authorizer;
+
+ authorizer = gdata_goa_authorizer_new (object);
+ self->service = gdata_documents_service_new (GDATA_AUTHORIZER (authorizer));
+ account_found = TRUE;
+ g_object_unref (authorizer);
+ }
+
+ g_free (provider_type);
+ g_free (account_identity);
+ g_object_unref (account);
+ }
+
+ if (!account_found)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid mount spec"));
+ goto out;
+ }
+
+ auth_domain = gdata_documents_service_get_primary_authorization_domain ();
+
+ error = NULL;
+ self->root = gdata_service_query_single_entry (GDATA_SERVICE (self->service),
+ auth_domain,
+ "root",
+ NULL,
+ GDATA_TYPE_DOCUMENTS_FOLDER,
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_vfs_backend_set_mount_spec (_self, spec);
+ g_vfs_backend_set_display_name (_self, self->account_identity);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_list_free_full (accounts, g_object_unref);
+ g_debug ("- mount\n");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_open_icon_for_read (GVfsBackend *_self,
+ GVfsJobOpenIconForRead *job,
+ const gchar *icon_id)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataAuthorizationDomain *auth_domain;
+ GInputStream *stream;
+
+ g_debug ("+ open_icon_for_read: %s\n", icon_id);
+
+ auth_domain = gdata_documents_service_get_primary_authorization_domain ();
+ stream = gdata_download_stream_new (GDATA_SERVICE (self->service), auth_domain, icon_id, cancellable);
+ if (stream == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error getting data from file"));
+ goto out;
+ }
+
+ g_vfs_job_open_for_read_set_handle (G_VFS_JOB_OPEN_FOR_READ (job), stream);
+ g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), TRUE);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_debug ("- open_icon_for_read\n");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_push (GVfsBackend *_self,
+ GVfsJobPush *job,
+ const gchar *destination,
+ const gchar *local_path,
+ GFileCopyFlags flags,
+ gboolean remove_source,
+ GFileProgressCallback progress_callback,
+ gpointer progress_callback_data)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataDocumentsDocument *document = NULL;
+ GDataDocumentsDocument *new_document = NULL;
+ GDataEntry *destination_parent;
+ GDataEntry *existing_entry;
+ GDataUploadStream *ostream = NULL;
+ GError *error;
+ GFile *local_file = NULL;
+ GFileInputStream *istream = NULL;
+ GFileInfo *info = NULL;
+ gboolean needs_overwrite = FALSE;
+ const gchar *content_type;
+ const gchar *title;
+ gchar *destination_basename = NULL;
+ gchar *entry_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ push: %s -> %s, %d\n", local_path, destination, flags);
+
+ if (flags & G_FILE_COPY_BACKUP)
+ {
+ /* Return G_IO_ERROR_NOT_SUPPORTED instead of
+ * G_IO_ERROR_CANT_CREATE_BACKUP to proceed with the GIO
+ * fallback copy.
+ */
+ g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation unsupported"));
+ goto out;
+ }
+
+ local_file = g_file_new_for_path (local_path);
+
+ error = NULL;
+ info = g_file_query_info (local_file,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE","
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ error = NULL;
+ destination_parent = resolve_dir_and_rebuild (self, destination, cancellable, &destination_basename, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ existing_entry = resolve_child (self, destination_parent, destination_basename);
+ if (existing_entry != NULL)
+ {
+ if (flags & G_FILE_COPY_OVERWRITE)
+ {
+ if (GDATA_IS_DOCUMENTS_FOLDER (existing_entry))
+ {
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_WOULD_MERGE,
+ _("Can't copy directory over directory"));
+ goto out;
+ }
+ else
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_IS_DIRECTORY,
+ _("Can't copy file over directory"));
+ goto out;
+ }
+ }
+ else
+ {
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_WOULD_RECURSE,
+ _("Can't recursively copy directory"));
+ goto out;
+ }
+ }
+
+ needs_overwrite = TRUE;
+ }
+ else
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
+ goto out;
+ }
+ }
+ else
+ {
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_WOULD_RECURSE,
+ _("Can't recursively copy directory"));
+ goto out;
+ }
+ }
+
+ g_debug (" will overwrite: %d\n", needs_overwrite);
+
+ error = NULL;
+ istream = g_file_read (local_file, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ content_type = g_file_info_get_content_type (info);
+
+ if (needs_overwrite)
+ {
+ document = g_object_ref (existing_entry);
+ title = gdata_entry_get_title (existing_entry);
+
+ error = NULL;
+ ostream = gdata_documents_service_update_document (self->service,
+ document,
+ title,
+ content_type,
+ cancellable,
+ &error);
+ }
+ else
+ {
+ document = gdata_documents_document_new (NULL);
+ title = destination_basename;
+ gdata_entry_set_title (GDATA_ENTRY (document), title);
+
+ error = NULL;
+ ostream = gdata_documents_service_upload_document (self->service,
+ document,
+ title,
+ content_type,
+ GDATA_DOCUMENTS_FOLDER (destination_parent),
+ cancellable,
+ &error);
+ }
+
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ error = NULL;
+ g_output_stream_splice (G_OUTPUT_STREAM (ostream),
+ G_INPUT_STREAM (istream),
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ error = NULL;
+ new_document = gdata_documents_service_finish_upload (self->service, ostream, &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, GDATA_ENTRY (new_document));
+ g_debug (" new entry path: %s\n", entry_path);
+
+ if (needs_overwrite)
+ remove_entry (self, existing_entry);
+
+ insert_entry (self, GDATA_ENTRY (new_document));
+ g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
+
+ if (remove_source)
+ {
+ error = NULL;
+ g_file_delete (local_file, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&document);
+ g_clear_object (&info);
+ g_clear_object (&istream);
+ g_clear_object (&local_file);
+ g_clear_object (&new_document);
+ g_clear_object (&ostream);
+ g_free (destination_basename);
+ g_free (entry_path);
+ g_debug ("- push\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+g_vfs_backend_google_query_fs_info (GVfsBackend *_self,
+ GVfsJobQueryFsInfo *job,
+ const gchar *filename,
+ GFileInfo *info,
+ GFileAttributeMatcher *matcher)
+{
+ GMountSpec *spec;
+ const gchar *type;
+
+ g_debug ("+ query_fs_info: %s\n", filename);
+
+ g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, FALSE);
+
+ spec = g_vfs_backend_get_mount_spec (_self);
+ type = g_mount_spec_get_type (spec);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, type);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ g_debug ("- query_fs_info\n");
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_query_info (GVfsBackend *_self,
+ GVfsJobQueryInfo *job,
+ const gchar *filename,
+ GFileQueryInfoFlags flags,
+ GFileInfo *info,
+ GFileAttributeMatcher *matcher)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataEntry *entry;
+ GError *error;
+ gboolean is_symlink = FALSE;
+ gchar *entry_path = NULL;
+ gchar *symlink_name = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ query_info: %s, %d\n", filename, flags);
+
+ error = NULL;
+ entry = resolve_and_rebuild (self, filename, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, entry);
+ if (g_strcmp0 (entry_path, filename) != 0) /* volatile */
+ {
+ is_symlink = TRUE;
+ symlink_name = g_path_get_basename (filename);
+ }
+
+ g_debug (" entry path: %s (%d)\n", entry_path, is_symlink);
+
+ error = NULL;
+ build_file_info (self, entry, flags, info, matcher, is_symlink, symlink_name, entry_path, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (entry_path);
+ g_free (symlink_name);
+ g_debug ("- query_info\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_query_info_on_read (GVfsBackend *_self,
+ GVfsJobQueryInfoRead *job,
+ GVfsBackendHandle handle,
+ GFileInfo *info,
+ GFileAttributeMatcher *matcher)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GDataEntry *entry;
+ GError *error;
+ GInputStream *stream = G_INPUT_STREAM (handle);
+ gboolean is_symlink = FALSE;
+ const gchar *filename;
+ gchar *entry_path = NULL;
+ gchar *symlink_name = NULL;
+
+ g_debug ("+ query_info_on_read: %p\n", handle);
+
+ entry = g_object_get_data (G_OBJECT (stream), "g-vfs-backend-google-entry");
+ filename = g_object_get_data (G_OBJECT (stream), "g-vfs-backend-google-filename");
+
+ entry_path = get_entry_path (self, entry);
+ if (g_strcmp0 (entry_path, filename) != 0) /* volatile */
+ {
+ is_symlink = TRUE;
+ symlink_name = g_path_get_basename (filename);
+ }
+
+ g_debug (" entry path: %s (%d)\n", entry_path, is_symlink);
+
+ error = NULL;
+ build_file_info (self,
+ entry,
+ G_FILE_QUERY_INFO_NONE,
+ info,
+ matcher,
+ is_symlink,
+ symlink_name,
+ entry_path,
+ &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (entry_path);
+ g_free (symlink_name);
+ g_debug ("- query_info_on_read\n");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gboolean
+g_vfs_backend_google_query_info_on_write (GVfsBackend *_self,
+ GVfsJobQueryInfoWrite *job,
+ GVfsBackendHandle handle,
+ GFileInfo *info,
+ GFileAttributeMatcher *matcher)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GError *error;
+ WriteHandle *wh = (WriteHandle *) handle;
+ gboolean is_symlink = FALSE;
+ gchar *entry_path = NULL;
+ gchar *symlink_name = NULL;
+
+ g_debug ("+ query_info_on_write: %p\n", handle);
+
+ entry_path = get_entry_path (self, wh->document);
+ if (g_strcmp0 (entry_path, wh->filename) != 0) /* volatile */
+ {
+ is_symlink = TRUE;
+ symlink_name = g_path_get_basename (wh->filename);
+ }
+
+ g_debug (" entry path: %s (%d)\n", entry_path, is_symlink);
+
+ error = NULL;
+ build_file_info (self,
+ wh->document,
+ G_FILE_QUERY_INFO_NONE,
+ info,
+ matcher,
+ is_symlink,
+ symlink_name,
+ entry_path,
+ &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (entry_path);
+ g_free (symlink_name);
+ g_debug ("- query_info_on_write\n");
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_open_for_read (GVfsBackend *_self,
+ GVfsJobOpenForRead *job,
+ const gchar *filename)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataEntry *entry;
+ GInputStream *stream;
+ GError *error;
+ gchar *content_type = NULL;
+ gchar *entry_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ open_for_read: %s\n", filename);
+
+ error = NULL;
+ entry = resolve_and_rebuild (self, filename, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, entry);
+ g_debug (" entry path: %s\n", entry_path);
+
+ if (GDATA_IS_DOCUMENTS_FOLDER (entry))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("Can't open directory"));
+ goto out;
+ }
+
+ content_type = get_content_type_from_entry (entry);
+ if (content_type == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid reply received"));
+ goto out;
+ }
+
+ /* While copying remote Drive content to local storage, we want to
+ * create Link-type desktop files because GLocalFile doesn't know
+ * about shortcuts. That might change in future.
+ */
+ if (g_str_has_prefix (content_type, CONTENT_TYPE_PREFIX_GOOGLE))
+ {
+ GDataLink *alternate;
+ GKeyFile *file;
+ const gchar *title;
+ const gchar *uri;
+ gchar *data;
+ gsize length;
+
+ file = g_key_file_new ();
+
+ title = gdata_entry_get_title (entry);
+ g_key_file_set_string (file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_NAME, title);
+ g_key_file_set_string (file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_TYPE, "Link");
+
+ alternate = gdata_entry_look_up_link (entry, GDATA_LINK_ALTERNATE);
+ uri = gdata_link_get_uri (alternate);
+ g_key_file_set_string (file, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_URL, uri);
+
+ data = g_key_file_to_data (file, &length, NULL);
+ stream = g_memory_input_stream_new_from_data (data, (gssize) length, g_free);
+
+ g_key_file_free (file);
+ }
+ else
+ {
+ GDataAuthorizationDomain *auth_domain;
+ const gchar *uri;
+
+ auth_domain = gdata_documents_service_get_primary_authorization_domain ();
+ uri = gdata_entry_get_content_uri (entry);
+ stream = gdata_download_stream_new (GDATA_SERVICE (self->service), auth_domain, uri, cancellable);
+ if (stream == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error getting data from file"));
+ goto out;
+ }
+ }
+
+ g_object_set_data_full (G_OBJECT (stream), "g-vfs-backend-google-entry", g_object_ref (entry), g_object_unref);
+ g_object_set_data_full (G_OBJECT (stream), "g-vfs-backend-google-filename", g_strdup (filename), g_free);
+ g_vfs_job_open_for_read_set_handle (job, stream);
+ g_vfs_job_open_for_read_set_can_seek (job, TRUE);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (content_type);
+ g_free (entry_path);
+ g_debug ("- open_for_read\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+read_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GInputStream *stream = G_INPUT_STREAM (source_object);
+ GVfsJobRead *job = G_VFS_JOB_READ (user_data);
+ gssize nread;
+
+ nread = g_input_stream_read_finish (stream, res, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_vfs_job_read_set_size (job, (gsize) nread);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_debug ("- read\n");
+}
+
+static gboolean
+g_vfs_backend_google_read (GVfsBackend *_self,
+ GVfsJobRead *job,
+ GVfsBackendHandle handle,
+ gchar *buffer,
+ gsize bytes_requested)
+{
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GInputStream *stream = G_INPUT_STREAM (handle);
+
+ g_debug ("+ read: %p\n", handle);
+ g_input_stream_read_async (stream, buffer, bytes_requested, G_PRIORITY_DEFAULT, cancellable, read_cb, job);
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_seek_on_read (GVfsBackend *_self,
+ GVfsJobSeekRead *job,
+ GVfsBackendHandle handle,
+ goffset offset,
+ GSeekType type)
+{
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GError *error;
+ GInputStream *stream = G_INPUT_STREAM (handle);
+ goffset cur_offset;
+
+ g_debug ("+ seek_on_read: %p\n", handle);
+
+ error = NULL;
+ g_seekable_seek (G_SEEKABLE (stream), offset, type, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ cur_offset = g_seekable_tell (G_SEEKABLE (stream));
+ g_vfs_job_seek_read_set_offset (job, cur_offset);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_debug ("- seek_on_read\n");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+close_read_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GInputStream *stream = G_INPUT_STREAM (source_object);
+ GVfsJobCloseRead *job = G_VFS_JOB_CLOSE_READ (user_data);
+
+ g_input_stream_close_finish (stream, res, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_object_unref (stream);
+ g_debug ("- close_read\n");
+}
+
+static gboolean
+g_vfs_backend_google_close_read (GVfsBackend *_self,
+ GVfsJobCloseRead *job,
+ GVfsBackendHandle handle)
+{
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GInputStream *stream = G_INPUT_STREAM (handle);
+
+ g_debug ("+ close_read: %p\n", handle);
+
+ g_input_stream_close_async (stream, G_PRIORITY_DEFAULT, cancellable, close_read_cb, job);
+
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_set_display_name (GVfsBackend *_self,
+ GVfsJobSetDisplayName *job,
+ const gchar *filename,
+ const gchar *display_name)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataAuthorizationDomain *auth_domain;
+ GDataEntry *entry;
+ GDataEntry *new_entry = NULL;
+ GError *error;
+ gchar *entry_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ set_display_name: %s, %s\n", filename, display_name);
+
+ error = NULL;
+ entry = resolve_and_rebuild (self, filename, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, entry);
+ g_debug (" entry path: %s\n", entry_path);
+
+ if (entry == self->root)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation unsupported"));
+ goto out;
+ }
+
+ gdata_entry_set_title (entry, display_name);
+ auth_domain = gdata_documents_service_get_primary_authorization_domain ();
+
+ error = NULL;
+ new_entry = gdata_service_update_entry (GDATA_SERVICE (self->service), auth_domain, entry, cancellable, &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ remove_entry (self, entry);
+ insert_entry (self, new_entry);
+ g_hash_table_foreach (self->monitors, emit_attribute_changed_event, entry_path);
+ g_vfs_job_set_display_name_set_new_path (job, entry_path);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&new_entry);
+ g_free (entry_path);
+ g_debug ("- set_display_name\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_create (GVfsBackend *_self,
+ GVfsJobOpenForWrite *job,
+ const gchar *filename,
+ GFileCreateFlags flags)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataDocumentsDocument *document = NULL;
+ GDataDocumentsEntry *new_document = NULL;
+ GDataEntry *existing_entry;
+ GDataEntry *parent;
+ GError *error;
+ WriteHandle *handle;
+ gchar *basename = NULL;
+ gchar *entry_path = NULL;
+ gchar *parent_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ create: %s, %d\n", filename, flags);
+
+ if (g_strcmp0 (filename, "/") == 0)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation unsupported"));
+ goto out;
+ }
+
+ error = NULL;
+ parent = resolve_dir_and_rebuild (self, filename, cancellable, &basename, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ parent_path = get_entry_path (self, parent);
+ g_debug (" parent path: %s\n", parent_path);
+
+ existing_entry = resolve_child (self, parent, basename);
+ if (existing_entry != NULL)
+ {
+ if (flags & G_FILE_CREATE_REPLACE_DESTINATION)
+ {
+ g_vfs_job_failed_literal (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Operation unsupported"));
+ goto out;
+ }
+ else
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
+ goto out;
+ }
+ }
+
+ document = gdata_documents_document_new (NULL);
+ gdata_entry_set_title (GDATA_ENTRY (document), basename);
+
+ error = NULL;
+ new_document = gdata_documents_service_add_entry_to_folder (self->service,
+ GDATA_DOCUMENTS_ENTRY (document),
+ GDATA_DOCUMENTS_FOLDER (parent),
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, GDATA_ENTRY (new_document));
+ g_debug (" new entry path: %s\n", entry_path);
+
+ insert_entry (self, GDATA_ENTRY (new_document));
+ g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
+
+ handle = write_handle_new (GDATA_ENTRY (new_document), NULL, filename);
+ g_vfs_job_open_for_write_set_handle (job, handle);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&document);
+ g_clear_object (&new_document);
+ g_free (basename);
+ g_free (entry_path);
+ g_free (parent_path);
+ g_debug ("- create\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_replace (GVfsBackend *_self,
+ GVfsJobOpenForWrite *job,
+ const gchar *filename,
+ const gchar *etag,
+ gboolean make_backup,
+ GFileCreateFlags flags)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataDocumentsDocument *document = NULL;
+ GDataDocumentsEntry *new_document = NULL;
+ GDataEntry *existing_entry;
+ GDataEntry *parent;
+ GDataUploadStream *stream = NULL;
+ GError *error;
+ WriteHandle *handle;
+ gboolean needs_overwrite = FALSE;
+ gchar *basename = NULL;
+ gchar *content_type = NULL;
+ gchar *entry_path = NULL;
+ gchar *parent_path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ replace: %s, %s, %d, %d\n", filename, etag, make_backup, flags);
+
+ if (make_backup)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_CANT_CREATE_BACKUP,
+ _("Backup file creation failed"));
+ goto out;
+ }
+
+ if (g_strcmp0 (filename, "/") == 0)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Can not create root directory"));
+ goto out;
+ }
+
+ error = NULL;
+ parent = resolve_dir_and_rebuild (self, filename, cancellable, &basename, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ parent_path = get_entry_path (self, parent);
+ g_debug (" parent path: %s\n", parent_path);
+
+ existing_entry = resolve_child (self, parent, basename);
+ if (existing_entry != NULL)
+ {
+ if (GDATA_IS_DOCUMENTS_FOLDER (existing_entry))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("Target file is a directory"));
+ goto out;
+ }
+ else if (!GDATA_IS_DOCUMENTS_DOCUMENT (existing_entry))
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_REGULAR_FILE,
+ _("Target file is not a regular file"));
+ goto out;
+ }
+
+ needs_overwrite = TRUE;
+ }
+
+ g_debug (" will overwrite: %d\n", needs_overwrite);
+
+ if (needs_overwrite)
+ {
+ const gchar *title;
+
+ entry_path = get_entry_path (self, existing_entry);
+ g_debug (" existing entry path: %s\n", entry_path);
+
+ title = gdata_entry_get_title (existing_entry);
+ content_type = get_content_type_from_entry (existing_entry);
+
+ error = NULL;
+ stream = gdata_documents_service_update_document (self->service,
+ GDATA_DOCUMENTS_DOCUMENT (existing_entry),
+ title,
+ content_type,
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ handle = write_handle_new (NULL, stream, filename);
+ }
+ else
+ {
+ document = gdata_documents_document_new (NULL);
+ gdata_entry_set_title (GDATA_ENTRY (document), basename);
+
+ error = NULL;
+ new_document = gdata_documents_service_add_entry_to_folder (self->service,
+ GDATA_DOCUMENTS_ENTRY (document),
+ GDATA_DOCUMENTS_FOLDER (parent),
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, GDATA_ENTRY (new_document));
+ g_debug (" new entry path: %s\n", entry_path);
+
+ insert_entry (self, GDATA_ENTRY (new_document));
+ g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
+
+ handle = write_handle_new (GDATA_ENTRY (new_document), NULL, filename);
+ }
+
+ g_vfs_job_open_for_write_set_handle (job, handle);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&document);
+ g_clear_object (&new_document);
+ g_clear_object (&stream);
+ g_free (basename);
+ g_free (content_type);
+ g_free (entry_path);
+ g_free (parent_path);
+ g_debug ("- replace\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_write (GVfsBackend *_self,
+ GVfsJobWrite *job,
+ GVfsBackendHandle handle,
+ gchar *buffer,
+ gsize buffer_size)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GError *error;
+ WriteHandle *wh = (WriteHandle *) handle;
+ gchar *entry_path = NULL;
+ gssize nwrite;
+
+ g_debug ("+ write: %p\n", handle);
+
+ if (wh->stream == NULL)
+ {
+ const gchar *title;
+ gchar *content_type = NULL;
+
+ title = gdata_entry_get_title (wh->document);
+ content_type = g_content_type_guess (title, (const guchar *) buffer, buffer_size, NULL);
+ g_debug (" content-type: %s\n", content_type);
+
+ error = NULL;
+ wh->stream = gdata_documents_service_update_document (self->service,
+ GDATA_DOCUMENTS_DOCUMENT (wh->document),
+ title,
+ content_type,
+ cancellable,
+ &error);
+ g_free (content_type);
+
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+ }
+
+ g_debug (" writing to stream: %p\n", wh->stream);
+
+ entry_path = get_entry_path (self, wh->document);
+ g_debug (" entry path: %s\n", entry_path);
+
+ error = NULL;
+ nwrite = g_output_stream_write (G_OUTPUT_STREAM (wh->stream),
+ buffer,
+ buffer_size,
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_hash_table_foreach (self->monitors, emit_changed_event, entry_path);
+ g_vfs_job_write_set_written_size (job, (gsize) nwrite);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (entry_path);
+ g_debug ("- write\n");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_close_write (GVfsBackend *_self,
+ GVfsJobCloseWrite *job,
+ GVfsBackendHandle handle)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
+ GDataDocumentsDocument *new_document = NULL;
+ GError *error;
+ WriteHandle *wh = (WriteHandle *) handle;
+ gchar *entry_path = NULL;
+
+ g_debug ("+ close_write: %p\n", handle);
+
+ if (!g_output_stream_is_closed (G_OUTPUT_STREAM (wh->stream)))
+ {
+ error = NULL;
+ g_output_stream_close (G_OUTPUT_STREAM (wh->stream), cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+ }
+
+ error = NULL;
+ new_document = gdata_documents_service_finish_upload (self->service, wh->stream, &error);
+ if (error != NULL)
+ {
+ sanitize_error (&error);
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+ else if (new_document == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error writing file"));
+ goto out;
+ }
+
+ entry_path = get_entry_path (self, GDATA_ENTRY (new_document));
+ g_debug (" new entry path: %s\n", entry_path);
+
+ remove_entry (self, wh->document);
+ insert_entry (self, GDATA_ENTRY (new_document));
+ g_hash_table_foreach (self->monitors, emit_changes_done_event, entry_path);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&new_document);
+ write_handle_free (wh);
+ g_free (entry_path);
+ g_debug ("- close_write\n");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+g_vfs_backend_google_dispose (GObject *_self)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+
+ if (self->entries_stale_timeout != 0)
+ {
+ g_source_remove (self->entries_stale_timeout);
+ self->entries_stale_timeout = 0;
+ }
+
+ if (self->dir_collisions != NULL)
+ {
+ g_list_free_full (self->dir_collisions, g_object_unref);
+ self->dir_collisions = NULL;
+ }
+
+ g_clear_object (&self->service);
+ g_clear_object (&self->root);
+ g_clear_object (&self->client);
+ g_clear_pointer (&self->entries, (GDestroyNotify) g_hash_table_unref);
+ g_clear_pointer (&self->dir_entries, (GDestroyNotify) g_hash_table_unref);
+
+ G_OBJECT_CLASS (g_vfs_backend_google_parent_class)->dispose (_self);
+}
+
+static void
+g_vfs_backend_google_finalize (GObject *_self)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+
+ g_hash_table_foreach (self->monitors, remove_monitor_weak_ref, self->monitors);
+ g_hash_table_unref (self->monitors);
+ g_free (self->account_identity);
+
+ g_rec_mutex_clear (&self->mutex);
+
+ G_OBJECT_CLASS (g_vfs_backend_google_parent_class)->finalize (_self);
+}
+
+static void
+g_vfs_backend_google_class_init (GVfsBackendGoogleClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
+
+ gobject_class->dispose = g_vfs_backend_google_dispose;
+ gobject_class->finalize = g_vfs_backend_google_finalize;
+
+ backend_class->try_close_read = g_vfs_backend_google_close_read;
+ backend_class->close_write = g_vfs_backend_google_close_write;
+ backend_class->copy = g_vfs_backend_google_copy;
+ backend_class->create = g_vfs_backend_google_create;
+ backend_class->try_create_dir_monitor = g_vfs_backend_google_create_dir_monitor;
+ backend_class->delete = g_vfs_backend_google_delete;
+ backend_class->enumerate = g_vfs_backend_google_enumerate;
+ backend_class->make_directory = g_vfs_backend_google_make_directory;
+ backend_class->mount = g_vfs_backend_google_mount;
+ backend_class->open_for_read = g_vfs_backend_google_open_for_read;
+ backend_class->open_icon_for_read = g_vfs_backend_google_open_icon_for_read;
+ backend_class->push = g_vfs_backend_google_push;
+ backend_class->try_query_fs_info = g_vfs_backend_google_query_fs_info;
+ backend_class->query_info = g_vfs_backend_google_query_info;
+ backend_class->query_info_on_read = g_vfs_backend_google_query_info_on_read;
+ backend_class->try_query_info_on_write = g_vfs_backend_google_query_info_on_write;
+ backend_class->seek_on_read = g_vfs_backend_google_seek_on_read;
+ backend_class->set_display_name = g_vfs_backend_google_set_display_name;
+ backend_class->try_read = g_vfs_backend_google_read;
+ backend_class->replace = g_vfs_backend_google_replace;
+ backend_class->write = g_vfs_backend_google_write;
+}
+
+static void
+g_vfs_backend_google_init (GVfsBackendGoogle *self)
+{
+ self->entries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+ self->dir_entries = g_hash_table_new_full (entries_in_folder_hash,
+ entries_in_folder_equal,
+ dir_entries_key_free,
+ g_object_unref);
+ self->monitors = g_hash_table_new (NULL, NULL);
+ g_rec_mutex_init (&self->mutex);
+ self->entries_stale = TRUE;
+}
diff --git a/daemon/gvfsbackendgoogle.h b/daemon/gvfsbackendgoogle.h
new file mode 100644
index 00000000..a5b3b2bc
--- /dev/null
+++ b/daemon/gvfsbackendgoogle.h
@@ -0,0 +1,60 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2014 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: Debarshi Ray <debarshir@gnome.org>
+ */
+
+#ifndef __G_VFS_BACKEND_GOOGLE_H__
+#define __G_VFS_BACKEND_GOOGLE_H__
+
+#include <gvfsbackend.h>
+
+G_BEGIN_DECLS
+
+#define G_VFS_TYPE_BACKEND_GOOGLE (g_vfs_backend_google_get_type())
+
+#define G_VFS_BACKEND_GOOGLE(o) \
+ (G_TYPE_CHECK_INSTANCE_CAST((o), \
+ G_VFS_TYPE_BACKEND_GOOGLE, GVfsBackendGoogle))
+
+#define G_VFS_BACKEND_GOOGLE_CLASS(k) \
+ (G_TYPE_CHECK_CLASS_CAST((k), \
+ G_VFS_TYPE_BACKEND_GOOGLE, GVfsBackendGoogleClass))
+
+#define G_VFS_IS_BACKEND_GOOGLE(o) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((o), \
+ G_VFS_TYPE_BACKEND_GOOGLE))
+
+#define G_VFS_IS_BACKEND_GOOGLE_CLASS(k) \
+ (G_TYPE_CHECK_CLASS_TYPE((k), \
+ G_VFS_TYPE_BACKEND_GOOGLE))
+
+#define G_VFS_BACKEND_GOOGLE_GET_CLASS(o) \
+ (G_TYPE_INSTANCE_GET_CLASS((o), \
+ G_VFS_TYPE_BACKEND_GOOGLE, GVfsBackendGoogleClass))
+
+typedef struct _GVfsBackendGoogle GVfsBackendGoogle;
+typedef struct _GVfsBackendGoogleClass GVfsBackendGoogleClass;
+
+GType g_vfs_backend_google_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __G_VFS_BACKEND_GOOGLE_H__ */