[gvfs/wip/rishi/goa: 3/5] Add GVfsBackendGoogle
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gvfs/wip/rishi/goa: 3/5] Add GVfsBackendGoogle
- Date: Tue, 30 Jun 2015 16:31:04 +0000 (UTC)
commit f28aa631de022d8c7aa67b4cae9b81c5f305b033
Author: Debarshi Ray <debarshir gnome org>
Date: Wed Oct 22 13:53:23 2014 +0200
Add GVfsBackendGoogle
Based on code written by Thibault Saunier.
https://bugzilla.gnome.org/show_bug.cgi?id=739008
client/Makefile.am | 1 +
client/gdaemonvfs.c | 2 +
client/googleuri.c | 194 ++++
configure.ac | 23 +
daemon/Makefile.am | 20 +
daemon/google.mount.in | 4 +
daemon/gvfsbackendgoogle.c | 2253 ++++++++++++++++++++++++++++++++++++++++++++
daemon/gvfsbackendgoogle.h | 62 ++
8 files changed, 2559 insertions(+), 0 deletions(-)
---
diff --git a/client/Makefile.am b/client/Makefile.am
index 8c7b76a..c9aa1f6 100644
--- a/client/Makefile.am
+++ b/client/Makefile.am
@@ -22,6 +22,7 @@ gvfsclientinclude_HEADERS = \
URI_PARSER_SOURCES = \
smburi.c \
+ googleuri.c \
httpuri.c \
afpuri.c \
$(NULL)
diff --git a/client/gdaemonvfs.c b/client/gdaemonvfs.c
index 11435e8..758fd98 100644
--- a/client/gdaemonvfs.c
+++ b/client/gdaemonvfs.c
@@ -1488,6 +1488,7 @@ g_daemon_vfs_class_init (GDaemonVfsClass *class)
/* Module API */
void g_vfs_uri_mapper_smb_register (GIOModule *module);
+void g_vfs_uri_mapper_google_register (GIOModule *module);
void g_vfs_uri_mapper_http_register (GIOModule *module);
void g_vfs_uri_mapper_afp_register (GIOModule *module);
@@ -1523,6 +1524,7 @@ g_io_module_load (GIOModule *module)
g_vfs_uri_mapper_register (module);
g_vfs_uri_mapper_smb_register (module);
+ g_vfs_uri_mapper_google_register (module);
g_vfs_uri_mapper_http_register (module);
g_vfs_uri_mapper_afp_register (module);
}
diff --git a/client/googleuri.c b/client/googleuri.c
new file mode 100644
index 0000000..3b6b640
--- /dev/null
+++ b/client/googleuri.c
@@ -0,0 +1,194 @@
+/* -*- 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>
+ */
+
+#include <config.h>
+
+#include <gio/gio.h>
+#include <gvfsurimapper.h>
+#include <gvfsuriutils.h>
+
+typedef struct _GVfsUriMapperGoogle GVfsUriMapperGoogle;
+typedef struct _GVfsUriMapperGoogleClass GVfsUriMapperGoogleClass;
+
+struct _GVfsUriMapperGoogle
+{
+ GVfsUriMapper parent;
+};
+
+struct _GVfsUriMapperGoogleClass
+{
+ GVfsUriMapperClass parent_class;
+};
+
+GType g_vfs_uri_mapper_google_get_type (void);
+void g_vfs_uri_mapper_google_register (GIOModule *module);
+
+G_DEFINE_DYNAMIC_TYPE (GVfsUriMapperGoogle, g_vfs_uri_mapper_google, G_VFS_TYPE_URI_MAPPER)
+
+static const gchar * const *
+google_get_handled_schemes (GVfsUriMapper *mapper)
+{
+ static const gchar *schemes[] = {
+ "google-drive",
+ NULL
+ };
+ return schemes;
+}
+
+static GMountSpec *
+google_from_uri (GVfsUriMapper *mapper,
+ const char *uri_str,
+ gchar **path)
+{
+ GMountSpec *spec = NULL;
+ GDecodedUri *uri = NULL;
+ gboolean has_host = FALSE;
+ gboolean has_user = FALSE;
+ gchar *identity = NULL;
+
+ uri = g_vfs_decode_uri (uri_str);
+
+ if (uri == NULL)
+ goto out;
+
+ if (g_ascii_strncasecmp (uri->scheme, "google-drive", 12) != 0)
+ goto out;
+
+ spec = g_mount_spec_new ("google-drive");
+
+ if (uri->host && *uri->host)
+ {
+ g_mount_spec_set (spec, "host", uri->host);
+ has_host = TRUE;
+ }
+
+ if (uri->userinfo && *uri->userinfo)
+ {
+ g_mount_spec_set (spec, "user", uri->userinfo);
+ has_user = TRUE;
+ }
+
+ if (has_host && has_user)
+ {
+ identity = g_strconcat (uri->userinfo, "@", uri->host, NULL);
+ g_mount_spec_set (spec, "goa-identity", identity);
+ }
+
+ *path = uri->path;
+ uri->path = NULL;
+
+ out:
+ g_free (identity);
+ g_vfs_decoded_uri_free (uri);
+ return spec;
+}
+
+static GMountSpec *
+google_get_mount_spec_for_path (GVfsUriMapper *mapper,
+ GMountSpec *spec,
+ const gchar *old_path,
+ const gchar *new_path)
+{
+ return NULL;
+}
+
+static const gchar * const *
+google_get_handled_mount_types (GVfsUriMapper *mapper)
+{
+ static const gchar *types[] = {
+ "google-drive",
+ NULL
+ };
+ return types;
+}
+
+static gchar *
+google_to_uri (GVfsUriMapper *mapper,
+ GMountSpec *spec,
+ const gchar *path,
+ gboolean allow_utf8)
+{
+ GDecodedUri *uri = NULL;
+ const gchar *host;
+ const gchar *type;
+ const gchar *user;
+ gchar *res = NULL;
+
+ type = g_mount_spec_get (spec, "type");
+ if (g_strcmp0 (type, "google-drive") != 0)
+ goto out;
+
+ host = g_mount_spec_get (spec, "host");
+ user = g_mount_spec_get (spec, "user");
+
+ uri = g_vfs_decoded_uri_new ();
+ uri->scheme = g_strdup (type);
+ uri->host = g_strdup (host);
+ uri->userinfo = g_strdup (user);
+ uri->path = g_strdup (path);
+
+ res = g_vfs_encode_uri (uri, allow_utf8);
+
+ out:
+ g_vfs_decoded_uri_free (uri);
+ return res;
+}
+
+static const gchar *
+google_to_uri_scheme (GVfsUriMapper *mapper,
+ GMountSpec *spec)
+{
+ const gchar *type;
+
+ type = g_mount_spec_get (spec, "type");
+ return (g_strcmp0 (type, "google-drive") == 0) ? type : NULL;
+}
+
+static void
+g_vfs_uri_mapper_google_class_finalize (GVfsUriMapperGoogleClass *klass)
+{
+}
+
+static void
+g_vfs_uri_mapper_google_class_init (GVfsUriMapperGoogleClass *class)
+{
+ GVfsUriMapperClass *mapper_class = G_VFS_URI_MAPPER_CLASS (class);
+
+ mapper_class->get_handled_schemes = google_get_handled_schemes;
+ mapper_class->from_uri = google_from_uri;
+ mapper_class->get_mount_spec_for_path = google_get_mount_spec_for_path;
+ mapper_class->get_handled_mount_types = google_get_handled_mount_types;
+ mapper_class->to_uri = google_to_uri;
+ mapper_class->to_uri_scheme = google_to_uri_scheme;
+}
+
+static void
+g_vfs_uri_mapper_google_init (GVfsUriMapperGoogle *vfs)
+{
+}
+
+void
+g_vfs_uri_mapper_google_register (GIOModule *module)
+{
+ g_vfs_uri_mapper_google_register_type (G_TYPE_MODULE (module));
+}
diff --git a/configure.ac b/configure.ac
index 93e3374..3d60a4d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -411,6 +411,28 @@ AC_SUBST(GOA_CFLAGS)
AM_CONDITIONAL(USE_GOA, [test "$msg_goa" = "yes"])
+dnl ****************************************************
+dnl *** Check if we should build with Google backend ***
+dnl ****************************************************
+AC_ARG_ENABLE(google, AS_HELP_STRING([--disable-google],[build without Google backend]))
+msg_google=no
+GOOGLE_LIBS=
+GOOGLE_CFLAGS=
+
+if test "x$enable_google" != "xno" ; then
+ PKG_CHECK_EXISTS(goa-1.0 >= 3.17.1 libgdata >= 0.17.2, msg_google=yes)
+
+ if test "x$msg_google" = "xyes"; then
+ PKG_CHECK_MODULES(GOOGLE, goa-1.0 libgdata)
+ AC_DEFINE(HAVE_GOOGLE, 1, [Define to 1 if Google is going to be built])
+ fi
+fi
+
+AC_SUBST(GOOGLE_LIBS)
+AC_SUBST(GOOGLE_CFLAGS)
+
+AM_CONDITIONAL(USE_GOOGLE, [test "$msg_google" = "yes"])
+
dnl *************************
dnl *** Check for gphoto2 ***
dnl *************************
@@ -930,6 +952,7 @@ echo "
hotplug backend: $msg_hotplug_backend
Blu-ray metadata support: $msg_bluray
+ Google support: $msg_google
HTTP/WebDAV support: $msg_http
Samba support: $msg_samba
FUSE support: $msg_fuse
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index ddfc1c9..1be228f 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -48,6 +48,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
@@ -430,6 +436,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 0000000..059b228
--- /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 0000000..e34f08a
--- /dev/null
+++ b/daemon/gvfsbackendgoogle.c
@@ -0,0 +1,2253 @@
+/* -*- 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 "gvfsjobcreatemonitor.h"
+#include "gvfsjobenumerate.h"
+#include "gvfsjobopenforread.h"
+#include "gvfsjobread.h"
+#include "gvfsjobseekread.h"
+#include "gvfsjobsetdisplayname.h"
+#include "gvfsmonitor.h"
+
+struct _GVfsBackendGoogle
+{
+ GVfsBackend parent;
+ GDataDocumentsService *service;
+ GDataEntry *root;
+ GHashTable *entries;
+ GHashTable *lookaside;
+ GHashTable *monitors;
+ GRecMutex mutex;
+ GoaClient *client;
+ gboolean entries_stale;
+ 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/"
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+escape_id (const gchar *id)
+{
+ return g_strconcat ("_", id, NULL);
+}
+
+static gchar *
+escape_filename (const gchar *filename)
+{
+ GString *escaped_filename;
+ gboolean separator_added = FALSE;
+ gchar *basename = NULL;
+ gchar *dirname = NULL;
+ gsize len;
+
+ len = strlen (filename);
+ escaped_filename = g_string_sized_new (len);
+ basename = g_path_get_basename (filename);
+ dirname = g_path_get_dirname (filename);
+
+ while (g_strcmp0 (basename, dirname) != 0)
+ {
+ gchar *escaped_basename;
+ gchar *tmp = NULL;
+
+ escaped_basename = escape_id (basename);
+ g_string_prepend (escaped_filename, escaped_basename);
+ g_string_prepend_c (escaped_filename, G_DIR_SEPARATOR);
+ separator_added = TRUE;
+ g_free (escaped_basename);
+
+ g_free (basename);
+ basename = g_path_get_basename (dirname);
+
+ tmp = dirname;
+ dirname = g_path_get_dirname (dirname);
+ g_free (tmp);
+ }
+
+ if (!separator_added)
+ g_string_prepend_c (escaped_filename, G_DIR_SEPARATOR);
+
+ g_free (basename);
+ g_free (dirname);
+ return g_string_free (escaped_filename, FALSE);
+}
+
+/* Takes a filename as supplied by the application and returns a value
+ * that is either:
+ * (a) guaranteed to be made up of persistent, server-defined IDs, or
+ * (b) can be used to look-up such a persistent filename through the
+ * lookaside cache
+ */
+static gchar *
+unescape_filename (GVfsBackendGoogle *self,
+ const gchar *filename,
+ gboolean *out_is_volatile)
+{
+ GString *unescaped_filename;
+ gboolean is_volatile;
+ gboolean separator_added = FALSE;
+ gchar *basename = NULL;
+ gchar *dirname = NULL;
+ gsize len;
+
+ /* If a path is in the lookaside cache then it is volatile, even if
+ * it looks like /_id10/_id2, and we don't perform the usual
+ * unescaping operation. See the copy job for an explanation.
+ */
+ if (g_hash_table_lookup (self->lookaside, filename) != NULL)
+ {
+ is_volatile = TRUE;
+ unescaped_filename = g_string_new (filename);
+ goto out;
+ }
+ else
+ {
+ is_volatile = FALSE;
+ }
+
+ len = strlen (filename);
+ unescaped_filename = g_string_sized_new (len);
+ basename = g_path_get_basename (filename);
+ dirname = g_path_get_dirname (filename);
+
+ while (g_strcmp0 (basename, dirname) != 0)
+ {
+ const gchar *unescaped_basename;
+ gchar *tmp = NULL;
+
+ if (g_str_has_prefix (basename, "_") && !g_str_has_prefix (basename, "__"))
+ {
+ unescaped_basename = basename + 1;
+ }
+ else
+ {
+ unescaped_basename = basename;
+ is_volatile = TRUE;
+ }
+
+ g_string_prepend (unescaped_filename, unescaped_basename);
+ g_string_prepend_c (unescaped_filename, G_DIR_SEPARATOR);
+ separator_added = TRUE;
+
+ g_free (basename);
+ basename = g_path_get_basename (dirname);
+
+ tmp = dirname;
+ dirname = g_path_get_dirname (dirname);
+ g_free (tmp);
+ }
+
+ if (!separator_added)
+ g_string_prepend_c (unescaped_filename, G_DIR_SEPARATOR);
+
+ out:
+ if (out_is_volatile != NULL)
+ *out_is_volatile = is_volatile;
+ g_free (basename);
+ g_free (dirname);
+ return g_string_free (unescaped_filename, FALSE);
+}
+
+/* Specifically meant for differentiating between /_id1/_id2 and
+ * /_id1/foo when creating a file or directory, and unescaping the
+ * dirname.
+ */
+static gchar *
+unescape_basename_and_map_dirname (GVfsBackendGoogle *self,
+ const gchar *filename,
+ gboolean *out_is_volatile)
+{
+ gboolean is_volatile;
+ gboolean is_dirname_volatile;
+ const gchar *unescaped_basename;
+ gchar *basename = NULL;
+ gchar *dirname = NULL;
+ gchar *ret_val = NULL;
+ gchar *unescaped_dirname = NULL;
+
+ basename = g_path_get_basename (filename);
+ if (g_str_has_prefix (basename, "_") && !g_str_has_prefix (basename, "__"))
+ {
+ unescaped_basename = basename + 1;
+ is_volatile = FALSE;
+ }
+ else
+ {
+ unescaped_basename = basename;
+ is_volatile = TRUE;
+ }
+
+ dirname = g_path_get_dirname (filename);
+ unescaped_dirname = unescape_filename (self, dirname, &is_dirname_volatile);
+ if (is_dirname_volatile)
+ {
+ const gchar *real_dirname;
+
+ real_dirname = g_hash_table_lookup (self->lookaside, unescaped_dirname);
+ if (real_dirname == NULL)
+ goto out;
+
+ g_free (unescaped_dirname);
+ unescaped_dirname = g_strdup (real_dirname);
+ }
+
+ ret_val = g_build_filename (unescaped_dirname, unescaped_basename, NULL);
+
+ if (out_is_volatile != NULL)
+ *out_is_volatile = is_volatile;
+
+ out:
+ g_free (unescaped_dirname);
+ g_free (dirname);
+ g_free (basename);
+ return ret_val;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+emit_event_internal (GVfsMonitor *monitor,
+ const gchar *filename,
+ GFileMonitorEvent event)
+{
+ const gchar *monitored_path;
+
+ monitored_path = g_object_get_data (G_OBJECT (monitor), "g-vfs-backend-google-path");
+ if (g_str_has_prefix (filename, monitored_path))
+ {
+ gchar *escaped_filename = NULL;
+
+ escaped_filename = escape_filename (filename);
+ g_vfs_monitor_emit_event (monitor, event, escaped_filename, NULL);
+ g_free (escaped_filename);
+ }
+}
+
+static void
+emit_create_event (gpointer monitor,
+ gpointer unused,
+ gpointer filename)
+{
+ emit_event_internal (G_VFS_MONITOR (monitor), filename, G_FILE_MONITOR_EVENT_CREATED);
+}
+
+static void
+emit_delete_event (gpointer monitor,
+ gpointer unused,
+ gpointer filename)
+{
+ emit_event_internal (G_VFS_MONITOR (monitor), filename, G_FILE_MONITOR_EVENT_DELETED);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+is_folder_or_root (const gchar *filename,
+ GDataEntry *entry,
+ gboolean *out_is_folder,
+ gboolean *out_is_root)
+{
+ gboolean is_folder = FALSE;
+ gboolean is_root = FALSE;
+
+ if (g_strcmp0 (filename, "/") == 0 && entry == NULL)
+ {
+ is_folder = TRUE;
+ is_root = TRUE;
+ }
+
+ if (entry != NULL && GDATA_IS_DOCUMENTS_FOLDER (entry))
+ is_folder = TRUE;
+
+ if (out_is_folder != NULL)
+ *out_is_folder = is_folder;
+
+ if (out_is_root != NULL)
+ *out_is_root = is_root;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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 void
+build_file_info (GVfsBackendGoogle *self,
+ const gchar *filename,
+ GDataEntry *entry,
+ GFileInfo *info,
+ GFileAttributeMatcher *matcher,
+ gboolean is_symlink,
+ const gchar *symlink_name,
+ GError **error)
+{
+ GFileType file_type;
+ GList *authors;
+ GMountSpec *spec;
+ gboolean content_type_override = FALSE;
+ gboolean is_folder;
+ gboolean is_root;
+ const gchar *etag;
+ const gchar *id;
+ const gchar *name;
+ const gchar *title;
+ gchar *escaped_name = NULL;
+ gchar *content_type = NULL;
+ gchar *copy_name = NULL;
+ gint64 atime;
+ gint64 ctime;
+ gint64 mtime;
+ gsize i;
+
+ is_folder_or_root (filename, entry, &is_folder, &is_root);
+ if (entry == NULL && !is_root)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or directory"));
+ goto out;
+ }
+
+ 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_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;
+ }
+
+ spec = g_vfs_backend_get_mount_spec (G_VFS_BACKEND (self));
+
+ if (is_symlink)
+ {
+ const gchar *mount_identity;
+ const gchar *type;
+ gchar *escaped_filename = NULL;
+ gchar *target_uri = NULL;
+
+ file_type = G_FILE_TYPE_SYMBOLIC_LINK;
+
+ type = g_mount_spec_get (spec, "type");
+ mount_identity = g_mount_spec_get (spec, "goa-identity");
+ escaped_filename = escape_filename (filename);
+ target_uri = g_strdup_printf ("%s://%s%s", type, mount_identity, escaped_filename);
+ g_file_info_set_symlink_target (info, target_uri);
+ g_free (target_uri);
+ g_free (escaped_filename);
+ }
+
+ if (content_type != NULL)
+ {
+ GIcon *icon;
+ const gchar *virtual_content_type = content_type;
+
+ /* We want native Drive content to open in the browser. We
+ * accomplish this by creating fake Link-type desktop files.
+ */
+ if (g_str_has_prefix (content_type, CONTENT_TYPE_PREFIX_GOOGLE))
+ {
+ content_type_override = TRUE;
+ virtual_content_type = "application/x-desktop";
+ }
+
+ g_file_info_set_content_type (info, virtual_content_type);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE,
virtual_content_type);
+
+ icon = g_content_type_get_icon (content_type);
+ g_file_info_set_icon (info, icon);
+ g_object_unref (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
+ {
+ escaped_name = escape_id (id);
+ name = escaped_name;
+ }
+
+ 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);
+
+ if (content_type_override)
+ copy_name = g_strconcat (title, ".desktop", NULL);
+ else
+ copy_name = g_strdup (title);
+
+ /* 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;
+
+ 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 (escaped_name);
+ g_free (content_type);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+find_root (GVfsBackendGoogle *self, GCancellable *cancellable, GError **error)
+{
+ GDataAuthorizationDomain *auth_domain;
+
+ if (self->root != NULL)
+ return;
+
+ auth_domain = gdata_documents_service_get_primary_authorization_domain ();
+ self->root = gdata_service_query_single_entry (GDATA_SERVICE (self->service),
+ auth_domain,
+ "root",
+ NULL,
+ GDATA_TYPE_DOCUMENTS_FOLDER,
+ cancellable,
+ error);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+get_parent_id (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))
+ {
+ gsize uri_prefix_len;
+
+ uri_prefix_len = strlen (URI_PREFIX);
+ ret_val = g_strdup (uri + uri_prefix_len);
+ break;
+ }
+ }
+
+ g_list_free (links);
+ return ret_val;
+}
+
+static gchar *
+get_entry_path (GVfsBackendGoogle *self, GDataEntry *entry)
+{
+ GString *path;
+ const gchar *base_id;
+ const gchar *root_id;
+ gchar *id = NULL;
+ gchar *ret_val = NULL;
+
+ base_id = gdata_entry_get_id (entry);
+ path = g_string_new (base_id);
+ g_string_prepend_c (path, '/');
+
+ id = get_parent_id (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 (parent_entry);
+ }
+
+
+ ret_val = g_strdup (path->str);
+
+ out:
+ g_free (id);
+ g_string_free (path, TRUE);
+ return ret_val;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static GoaClient *
+get_goa_client_sync (GError **error)
+{
+ static GoaClient *client = NULL;
+ static GError *_error = NULL;
+ static volatile gsize initialized = 0;
+
+ if (g_once_init_enter (&initialized))
+ {
+ client = goa_client_new_sync (NULL, &_error);
+ g_once_init_leave (&initialized, 1);
+ }
+
+ if (_error != NULL && error != NULL)
+ *error = g_error_copy (_error);
+
+ return client;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+get_parent_basename (const gchar *filename)
+{
+ gchar *parent;
+ gchar *ret_val;
+
+ parent = g_path_get_dirname (filename);
+ ret_val = g_path_get_basename (parent);
+
+ g_free (parent);
+ return ret_val;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+rebuild_entries (GVfsBackendGoogle *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GDataDocumentsFeed *feed = NULL;
+ GDataDocumentsQuery *query = NULL;
+ GError *local_error;
+ gboolean succeeded_once = FALSE;
+
+ local_error = NULL;
+ find_root (self, cancellable, &local_error);
+ if (local_error != NULL)
+ {
+ g_propagate_error (error, local_error);
+ goto out;
+ }
+
+ query = gdata_documents_query_new_with_limits (NULL, 1, MAX_RESULTS);
+ gdata_documents_query_set_show_folders (query, TRUE);
+
+ while (TRUE)
+ {
+ 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
+ {
+ g_propagate_error (error, local_error);
+ }
+
+ break;
+ }
+
+ if (!succeeded_once)
+ {
+ g_hash_table_remove_all (self->entries);
+ 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);
+ const gchar *id;
+
+ id = gdata_entry_get_id (entry);
+ g_hash_table_insert (self->entries, g_strdup (id), g_object_ref (entry));
+ }
+
+ gdata_query_next_page (GDATA_QUERY (query));
+ g_clear_object (&feed);
+ }
+
+ out:
+ g_clear_object (&feed);
+ g_clear_object (&query);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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
+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 (job);
+ 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, g_object_ref (job));
+
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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 *new_entry = NULL;
+ GDataDocumentsFolder *destination_parent;
+ GDataEntry *source_entry;
+ GError *error;
+ gboolean is_folder;
+ gboolean is_root;
+ gboolean is_volatile_source;
+ const gchar *id;
+ const gchar *real_destination_parent_path;
+ const gchar *real_parent_path;
+ const gchar *real_source;
+ gchar *destination_parent_id = NULL;
+ gchar *destination_parent_path = NULL;
+ gchar *path = NULL;
+ gchar *source_id = NULL;
+ gchar *unescaped_destination = NULL;
+ gchar *unescaped_source = NULL;
+
+ /* The destination is always volatile. It will look like /_id1/_id2
+ * -> /_id10/_id2, whereas in reality it will be /_id1/_id2 ->
+ * /_id10/_id20. This will confuse our escaping logic when
+ * /_id10/_id2 is passed to a any subsequent job, and make it think
+ * that it is a non-volatile path. Hence, we add /_id10/_id2 to the
+ * lookaside cache at the end of the copy so that subsequent jobs
+ * can recognize it as a volatile path.
+ */
+
+ g_rec_mutex_lock (&self->mutex);
+ unescaped_destination = unescape_basename_and_map_dirname (self, destination, NULL);
+ unescaped_source = unescape_filename (self, source, &is_volatile_source);
+ g_debug ("+ copy: %s (%s, %d) -> %s (%s, 1)\n",
+ source,
+ unescaped_source,
+ is_volatile_source,
+ destination,
+ unescaped_destination);
+ source = unescaped_source;
+
+ error = NULL;
+ find_root (self, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ if (is_volatile_source)
+ {
+ real_source = g_hash_table_lookup (self->lookaside, source);
+ if (real_source == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+
+ source = real_source;
+ }
+
+ source_id = g_path_get_basename (source);
+ source_entry = g_hash_table_lookup (self->entries, source_id);
+
+ destination_parent_path = g_path_get_dirname (unescaped_destination);
+ real_destination_parent_path = g_hash_table_lookup (self->lookaside, destination_parent_path);
+ if (real_destination_parent_path != NULL)
+ {
+ g_free (destination_parent_path);
+ destination_parent_path = g_strdup (real_destination_parent_path);
+ }
+
+ is_folder_or_root (destination_parent_path, NULL, NULL, &is_root);
+ if (is_root)
+ {
+ destination_parent = GDATA_DOCUMENTS_FOLDER (self->root);
+ }
+ else
+ {
+ destination_parent_id = g_path_get_basename (destination_parent_path);
+ destination_parent = g_hash_table_lookup (self->entries, destination_parent_id);
+ }
+
+ if (source_entry == NULL || destination_parent == NULL)
+ {
+ 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;
+ }
+ }
+
+ source_entry = g_hash_table_lookup (self->entries, source_id);
+ is_folder_or_root (source, source_entry, &is_folder, NULL);
+ if (is_folder)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_WOULD_RECURSE,
+ _("Can't recursively copy directory"));
+ goto out;
+ }
+
+ destination_parent = g_hash_table_lookup (self->entries, destination_parent_id);
+ if (source_entry == NULL || destination_parent == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or directory"));
+ goto out;
+ }
+
+ error = NULL;
+ new_entry = gdata_documents_service_add_entry_to_folder (self->service,
+ GDATA_DOCUMENTS_ENTRY (source_entry),
+ destination_parent,
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ id = gdata_entry_get_id (GDATA_ENTRY (new_entry));
+ g_hash_table_insert (self->entries, g_strdup (id), g_object_ref (new_entry));
+
+ path = get_entry_path (self, GDATA_ENTRY (new_entry));
+ if (path != NULL)
+ {
+ g_debug (" insert lookaside: %s -> %s\n", destination, path);
+ g_hash_table_insert (self->lookaside, g_strdup (destination), g_strdup (path));
+ g_hash_table_foreach (self->monitors, emit_create_event, (gpointer) path);
+ }
+
+ self->entries_stale = TRUE;
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&new_entry);
+ g_free (path);
+ g_free (destination_parent_id);
+ g_free (destination_parent_path);
+ g_free (source_id);
+ g_free (unescaped_source);
+ g_free (unescaped_destination);
+ 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;
+ gboolean is_folder;
+ gboolean is_root;
+ gboolean is_volatile;
+ gchar *id = NULL;
+ gchar *unescaped_filename = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ unescaped_filename = unescape_filename (self, filename, &is_volatile);
+ g_debug ("+ create_dir_monitor: %s (%s, %d)\n", filename, unescaped_filename, is_volatile);
+ filename = unescaped_filename;
+
+ if (is_volatile)
+ {
+ const gchar *real_filename;
+
+ real_filename = g_hash_table_lookup (self->lookaside, filename);
+ if (real_filename == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+
+ filename = real_filename;
+ }
+
+ id = g_path_get_basename (filename);
+ entry = g_hash_table_lookup (self->entries, id);
+ is_folder_or_root (filename, entry, NULL, &is_root);
+
+ if (entry == NULL && !is_root)
+ {
+ 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;
+ }
+
+ entry = g_hash_table_lookup (self->entries, id);
+ if (entry == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+ }
+
+ is_folder_or_root (filename, entry, &is_folder, NULL);
+ if (!is_folder)
+ {
+ 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 (filename), 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 (id);
+ g_free (unescaped_filename);
+ 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;
+ gboolean is_root;
+ gboolean is_volatile;
+ gchar *path = NULL;
+ gchar *id = NULL;
+ gchar *unescaped_filename = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ unescaped_filename = unescape_filename (self, filename, &is_volatile);
+ g_debug ("+ delete: %s (%s, %d)\n", filename, unescaped_filename, is_volatile);
+ filename = unescaped_filename;
+
+ if (is_volatile)
+ {
+ const gchar *real_filename;
+
+ real_filename = g_hash_table_lookup (self->lookaside, filename);
+ if (real_filename == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+
+ filename = real_filename;
+ }
+
+ is_folder_or_root (filename, NULL, NULL, &is_root);
+ if (is_root)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Can not delete the root directory"));
+ goto out;
+ }
+
+ id = g_path_get_basename (filename);
+ entry = g_hash_table_lookup (self->entries, id);
+
+ if (entry == NULL)
+ {
+ 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;
+ }
+
+ entry = g_hash_table_lookup (self->entries, id);
+ if (entry == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+ }
+
+ auth_domain = gdata_documents_service_get_primary_authorization_domain ();
+ path = get_entry_path (self, entry);
+
+ error = NULL;
+ gdata_service_delete_entry (GDATA_SERVICE (self->service), auth_domain, entry, 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_remove (self->entries, id);
+ if (path != NULL)
+ g_hash_table_foreach (self->monitors, emit_delete_event, (gpointer) path);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (path);
+ g_free (id);
+ g_free (unescaped_filename);
+ 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;
+ gboolean is_volatile;
+ gchar *unescaped_filename = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ unescaped_filename = unescape_filename (self, filename, &is_volatile);
+ g_debug ("+ enumerate: %s (%s, %d)\n", filename, unescaped_filename, is_volatile);
+ filename = unescaped_filename;
+
+ if (is_volatile)
+ {
+ const gchar *real_filename;
+
+ real_filename = g_hash_table_lookup (self->lookaside, filename);
+ if (real_filename == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+
+ filename = real_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;
+ }
+
+ self->entries_stale = FALSE;
+ }
+
+ 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);
+ if (path != NULL)
+ {
+ gchar *parent_path;
+
+ parent_path = g_path_get_dirname (path);
+ if (g_strcmp0 (filename, parent_path) == 0)
+ {
+ GFileInfo *info;
+
+ info = g_file_info_new ();
+ build_file_info (self, path, entry, info, matcher, FALSE, 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 (unescaped_filename);
+ 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_entry = NULL;
+ GDataDocumentsFolder *folder = NULL;
+ GDataDocumentsFolder *parent;
+ GDataEntry *source_entry = NULL;
+ GError *error;
+ gboolean is_display_name;
+ gboolean is_root;
+ gboolean needs_rebuild = FALSE;
+ const gchar *id;
+ const gchar *real_parent_path;
+ gchar *parent_id = NULL;
+ gchar *parent_path = NULL;
+ gchar *path = NULL;
+ gchar *source_id = NULL;
+ gchar *title = NULL;
+ gchar *unescaped_filename = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ unescaped_filename = unescape_basename_and_map_dirname (self, filename, &is_display_name);
+ g_debug ("+ make_directory: %s (%s, %d)\n", filename, unescaped_filename, is_display_name);
+
+ if (unescaped_filename == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or directory"));
+ goto out;
+ }
+
+ is_folder_or_root (unescaped_filename, NULL, NULL, &is_root);
+ if (is_root)
+ {
+ 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;
+ find_root (self, cancellable, &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ parent_path = g_path_get_dirname (unescaped_filename);
+ is_folder_or_root (parent_path, NULL, NULL, &is_root);
+ if (is_root)
+ {
+ parent = GDATA_DOCUMENTS_FOLDER (self->root);
+ }
+ else
+ {
+ parent_id = g_path_get_basename (parent_path);
+ parent = g_hash_table_lookup (self->entries, parent_id);
+ if (parent == NULL)
+ needs_rebuild = TRUE;
+ }
+
+ if (is_display_name)
+ {
+ title = g_path_get_basename (unescaped_filename);
+ }
+ else
+ {
+ source_id = g_path_get_basename (unescaped_filename);
+ source_entry = g_hash_table_lookup (self->entries, source_id);
+ if (source_entry == NULL)
+ needs_rebuild = TRUE;
+ }
+
+ 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;
+ }
+ }
+
+ parent = g_hash_table_lookup (self->entries, parent_id);
+ if (parent == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or directory"));
+ goto out;
+ }
+
+ if (!is_display_name)
+ {
+ source_entry = g_hash_table_lookup (self->entries, source_id);
+ if (source_entry == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+
+ title = g_strdup (gdata_entry_get_title (source_entry));
+ }
+
+ folder = gdata_documents_folder_new (NULL);
+ gdata_entry_set_title (GDATA_ENTRY (folder), title);
+
+ error = NULL;
+ new_entry = gdata_documents_service_add_entry_to_folder (self->service,
+ GDATA_DOCUMENTS_ENTRY (folder),
+ parent,
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ id = gdata_entry_get_id (GDATA_ENTRY (new_entry));
+ g_hash_table_insert (self->entries, g_strdup (id), g_object_ref (new_entry));
+
+ path = get_entry_path (self, GDATA_ENTRY (new_entry));
+ if (path != NULL)
+ {
+ g_debug (" insert lookaside: %s -> %s\n", filename, path);
+ g_hash_table_insert (self->lookaside, g_strdup (filename), g_strdup (path));
+ g_hash_table_foreach (self->monitors, emit_create_event, (gpointer) path);
+ }
+
+ self->entries_stale = TRUE;
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&new_entry);
+ g_clear_object (&folder);
+ g_free (source_id);
+ g_free (parent_id);
+ g_free (parent_path);
+ g_free (path);
+ g_free (title);
+ g_free (unescaped_filename);
+ 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);
+ GError *error;
+ GList *accounts = NULL;
+ GList *l;
+ gboolean account_found = FALSE;
+ const gchar *mount_identity;
+
+ g_debug ("+ mount\n");
+
+ if (self->client == NULL)
+ {
+ error = NULL;
+ self->client = get_goa_client_sync (&error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+ }
+
+ mount_identity = g_mount_spec_get (spec, "goa-identity");
+ if (mount_identity == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid mount spec"));
+ goto out;
+ }
+
+ 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, mount_identity) == 0)
+ {
+ GDataGoaAuthorizer *authorizer;
+
+ authorizer = gdata_goa_authorizer_new (object);
+
+ if (self->service != NULL)
+ {
+ g_warning ("Expected NULL service, but found %p", self->service);
+ g_clear_object (&self->service);
+ }
+
+ 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,
+ _("Failed to find a matching GOA account: %s"),
+ mount_identity);
+ goto out;
+ }
+
+ g_vfs_backend_set_mount_spec (_self, spec);
+ g_vfs_backend_set_display_name (_self, mount_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_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;
+ gboolean is_folder;
+ gboolean is_root;
+ gboolean is_volatile;
+ gchar *content_type = NULL;
+ gchar *id = NULL;
+ gchar *unescaped_filename = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ unescaped_filename = unescape_filename (self, filename, &is_volatile);
+ g_debug ("+ open_for_read: %s (%s, %d)\n", filename, unescaped_filename, is_volatile);
+ filename = unescaped_filename;
+
+ if (is_volatile)
+ {
+ const gchar *real_filename;
+
+ real_filename = g_hash_table_lookup (self->lookaside, filename);
+ if (real_filename == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+
+ filename = real_filename;
+ }
+
+ id = g_path_get_basename (filename);
+ entry = g_hash_table_lookup (self->entries, id);
+
+ is_folder_or_root (filename, entry, NULL, &is_root);
+ if (is_root)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("Can't open directory"));
+ goto out;
+ }
+
+ if (entry == NULL)
+ {
+ 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;
+ }
+
+ entry = g_hash_table_lookup (self->entries, id);
+ if (entry == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+ }
+
+ is_folder_or_root (filename, entry, &is_folder, NULL);
+ if (is_folder)
+ {
+ 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, _("Unknown content type"));
+ goto out;
+ }
+
+ /* We want native Drive content to open in the browser. We
+ * accomplish this by creating fake Link-type desktop files.
+ */
+ 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);
+ }
+
+ g_object_set_data_full (G_OBJECT (stream), "g-vfs-backend-google-entry", g_object_ref (entry),
g_object_unref);
+
+ 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 (id);
+ g_free (unescaped_filename);
+ g_debug ("- open_for_read\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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, _("Failed to initiate download"));
+ 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;
+ GDataDocumentsFolder *parent = NULL;
+ GDataUploadStream *ostream = NULL;
+ GError *error;
+ GFile *local_file = NULL;
+ GFileInputStream *istream = NULL;
+ GFileInfo *info = NULL;
+ gboolean is_root;
+ const gchar *content_type;
+ const gchar *id;
+ const gchar *real_parent_path;
+ const gchar *title;
+ gchar *parent_id = NULL;
+ gchar *parent_path = NULL;
+ gchar *path = NULL;
+ gchar *unescaped_destination = NULL;
+
+ /* The destination is always volatile. It will look like /foo/bar
+ * -> /_id1/bar, whereas in reality it will be /foo/bar ->
+ * /_id1/_id2.
+ */
+
+ g_rec_mutex_lock (&self->mutex);
+ unescaped_destination = unescape_basename_and_map_dirname (self, destination, NULL);
+ g_debug ("+ push: %s -> %s (%s, 1)\n", local_path, destination, unescaped_destination);
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ parent_path = g_path_get_dirname (unescaped_destination);
+ real_parent_path = g_hash_table_lookup (self->lookaside, parent_path);
+ if (real_parent_path != NULL)
+ {
+ g_free (parent_path);
+ parent_path = g_strdup (real_parent_path);
+ }
+
+ is_folder_or_root (parent_path, NULL, NULL, &is_root);
+ if (!is_root)
+ {
+ parent_id = g_path_get_basename (parent_path);
+ parent = g_hash_table_lookup (self->entries, parent_id);
+
+ if (parent == NULL)
+ {
+ 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;
+ }
+
+ parent = g_hash_table_lookup (self->entries, parent_id);
+ if (parent == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+ }
+ }
+
+ document = gdata_documents_document_new (NULL);
+ title = g_file_info_get_display_name (info);
+ gdata_entry_set_title (GDATA_ENTRY (document), title);
+
+ content_type = g_file_info_get_content_type (info);
+
+ error = NULL;
+ ostream = gdata_documents_service_upload_document (self->service,
+ document,
+ title,
+ content_type,
+ parent,
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ 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)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ id = gdata_entry_get_id (GDATA_ENTRY (new_document));
+ g_hash_table_insert (self->entries, g_strdup (id), g_object_ref (new_document));
+
+ path = get_entry_path (self, GDATA_ENTRY (new_document));
+ if (path != NULL)
+ {
+ g_debug (" insert lookaside: %s -> %s\n", destination, path);
+ g_hash_table_insert (self->lookaside, g_strdup (destination), g_strdup (path));
+ g_hash_table_foreach (self->monitors, emit_create_event, (gpointer) path);
+ }
+
+ self->entries_stale = TRUE;
+
+ 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 (parent_id);
+ g_free (parent_path);
+ g_free (path);
+ g_free (unescaped_destination);
+ 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)
+{
+ GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
+ 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_root;
+ gboolean is_symlink = FALSE;
+ gboolean is_volatile;
+ gchar *id = NULL;
+ gchar *symlink_name = NULL;
+ gchar *unescaped_filename = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ unescaped_filename = unescape_filename (self, filename, &is_volatile);
+ g_debug ("+ query_info: %s (%s, %d), %d\n", filename, unescaped_filename, is_volatile, flags);
+ filename = unescaped_filename;
+
+ if (is_volatile)
+ {
+ const gchar *real_filename;
+
+ real_filename = g_hash_table_lookup (self->lookaside, filename);
+ if (real_filename == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+
+ filename = real_filename;
+ if ((flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS) != 0)
+ {
+ is_symlink = TRUE;
+ symlink_name = g_path_get_basename (filename);
+ }
+ }
+
+ id = g_path_get_basename (filename);
+ entry = g_hash_table_lookup (self->entries, id);
+ is_folder_or_root (filename, entry, NULL, &is_root);
+
+ if (entry == NULL && !is_root)
+ {
+ 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;
+ }
+
+ entry = g_hash_table_lookup (self->entries, id);
+ }
+
+ error = NULL;
+ build_file_info (self, filename, entry, info, matcher, is_symlink, symlink_name, &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 (id);
+ g_free (symlink_name);
+ g_free (unescaped_filename);
+ 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);
+ gchar *path = NULL;
+
+ g_debug ("+ query_info_on_read: %p\n", handle);
+
+ entry = g_object_get_data (G_OBJECT (stream), "g-vfs-backend-google-entry");
+ path = get_entry_path (self, entry);
+ if (path == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or directory"));
+ goto out;
+ }
+
+ error = NULL;
+ build_file_info (self, path, entry, info, matcher, FALSE, NULL, &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 (path);
+ g_debug ("- query_info_on_read\n");
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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
+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;
+ gboolean is_root;
+ gboolean is_volatile;
+ const gchar *new_id;
+ gchar *id = NULL;
+ gchar *unescaped_filename = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ unescaped_filename = unescape_filename (self, filename, &is_volatile);
+ g_debug ("+ set_display_name: %s (%s, %d), %s\n", filename, unescaped_filename, is_volatile, display_name);
+
+ if (is_volatile)
+ {
+ const gchar *real_filename;
+
+ real_filename = g_hash_table_lookup (self->lookaside, unescaped_filename);
+ if (real_filename == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ goto out;
+ }
+
+ g_free (unescaped_filename);
+ unescaped_filename = g_strdup (real_filename);
+ }
+
+ is_folder_or_root (unescaped_filename, NULL, NULL, &is_root);
+ if (is_root)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job),
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_SUPPORTED,
+ _("Can not rename the root directory"));
+ goto out;
+ }
+
+ id = g_path_get_basename (unescaped_filename);
+ entry = g_hash_table_lookup (self->entries, id);
+
+ if (entry == NULL)
+ {
+ 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;
+ }
+
+ entry = g_hash_table_lookup (self->entries, id);
+ if (entry == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or
directory"));
+ 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)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ g_hash_table_remove (self->entries, id);
+
+ new_id = gdata_entry_get_id (new_entry);
+ g_hash_table_insert (self->entries, g_strdup (new_id), g_object_ref (new_entry));
+
+ g_vfs_job_set_display_name_set_new_path (job, filename);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&new_entry);
+ g_free (id);
+ g_free (unescaped_filename);
+ g_debug ("- set_display_name\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+read_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ GInputStream *stream = G_INPUT_STREAM (source_object);
+ GVfsJobRead *job = G_VFS_JOB_READ (user_data);
+ gssize nread;
+
+ error = NULL;
+ 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_object_unref (job);
+ 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,
+ g_object_ref (job));
+
+ return TRUE;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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;
+ }
+
+ 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_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_unref (self->lookaside);
+
+ g_hash_table_foreach (self->monitors, remove_monitor_weak_ref, self->monitors);
+ g_hash_table_unref (self->monitors);
+
+ 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->copy = g_vfs_backend_google_copy;
+ 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->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;
+}
+
+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->lookaside = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ self->monitors = g_hash_table_new (NULL, NULL);
+ g_rec_mutex_init (&self->mutex);
+ self->entries_stale = TRUE;
+}
+
+GVfsBackend *
+g_vfs_backend_google_new (void)
+{
+ return g_object_new (G_VFS_TYPE_BACKEND_GOOGLE, NULL);
+}
diff --git a/daemon/gvfsbackendgoogle.h b/daemon/gvfsbackendgoogle.h
new file mode 100644
index 0000000..21b209f
--- /dev/null
+++ b/daemon/gvfsbackendgoogle.h
@@ -0,0 +1,62 @@
+/* -*- 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;
+
+GVfsBackend *g_vfs_backend_google_new (void);
+
+G_END_DECLS
+
+#endif /* __G_VFS_BACKEND_GOOGLE_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]