[gvfs/wip/rishi/goa: 2/4] Add GVfsBackendGoogle
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gvfs/wip/rishi/goa: 2/4] Add GVfsBackendGoogle
- Date: Mon, 22 Jun 2015 15:47:21 +0000 (UTC)
commit 053490fcc48f3d05251d53721cd81695316575ab
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 | 1644 ++++++++++++++++++++++++++++++++++++++++++++
daemon/gvfsbackendgoogle.h | 62 ++
8 files changed, 1950 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..6ffe08c 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.15.1 libgdata >= 0.13.1, 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 2d52f0a..c10a924 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
@@ -429,6 +435,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..fa9f219
--- /dev/null
+++ b/daemon/gvfsbackendgoogle.c
@@ -0,0 +1,1644 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* gvfs - extensions for gio
+ *
+ * Copyright (C) 2009 Thibault Saunier
+ * 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>
+ * Thibault Saunier <saunierthibault gmail com>
+ */
+
+#include <config.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 "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;
+ 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 MAX_RESULTS_INITIAL 50
+#define MAX_RESULTS_FACTOR 2
+
+#define REBUILD_ENTRIES_TIMEOUT 60 /* s */
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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))
+ g_vfs_monitor_emit_event (monitor, event, filename, NULL);
+}
+
+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
+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)
+{
+ gchar *ret_val = NULL;
+
+ if (!GDATA_IS_DOCUMENTS_DOCUMENT (entry))
+ goto out;
+
+ if (GDATA_IS_DOCUMENTS_DRAWING (entry))
+ {
+ ret_val = g_strdup ("image/png");
+ }
+ else if (GDATA_IS_DOCUMENTS_PDF (entry))
+ {
+ ret_val = g_strdup ("application/pdf");
+ }
+ else if (GDATA_IS_DOCUMENTS_PRESENTATION (entry))
+ {
+ ret_val = g_strdup ("application/vnd.ms-powerpoint");
+ }
+ else if (GDATA_IS_DOCUMENTS_SPREADSHEET (entry))
+ {
+ ret_val = g_strdup ("application/vnd.oasis.opendocument.spreadsheet");
+ }
+ else if (GDATA_IS_DOCUMENTS_TEXT (entry))
+ {
+ ret_val = g_strdup ("application/vnd.oasis.opendocument.text");
+ }
+ else
+ {
+ GList *categories;
+ GList *l;
+
+ categories = gdata_entry_get_categories (entry);
+ for (l = categories; l != NULL; l = l->next)
+ {
+ GDataCategory *category = GDATA_CATEGORY (l->data);
+ const gchar *scheme;
+ const gchar *term;
+
+ scheme = gdata_category_get_scheme (category);
+ term = gdata_category_get_term (category);
+ if (g_strcmp0 (scheme, CATEGORY_SCHEMA_KIND) == 0 &&
+ g_strcmp0 (term, CATEGORY_SCHEMA_KIND_FILE) == 0)
+ {
+ ret_val = g_strdup (gdata_category_get_label (category));
+ break;
+ }
+ }
+ }
+
+ out:
+ return ret_val;
+}
+
+static gchar *
+get_export_format_from_entry (GDataEntry *entry)
+{
+ gchar *ret_val = NULL;
+
+ if (!GDATA_IS_DOCUMENTS_DOCUMENT (entry))
+ goto out;
+
+ if (GDATA_IS_DOCUMENTS_DRAWING (entry))
+ {
+ ret_val = g_strdup (GDATA_DOCUMENTS_DRAWING_PNG);
+ }
+ else if (GDATA_IS_DOCUMENTS_PDF (entry))
+ {
+ ret_val = g_strdup (GDATA_DOCUMENTS_TEXT_PDF);
+ }
+ else if (GDATA_IS_DOCUMENTS_PRESENTATION (entry))
+ {
+ ret_val = g_strdup (GDATA_DOCUMENTS_PRESENTATION_PPT);
+ }
+ else if (GDATA_IS_DOCUMENTS_SPREADSHEET (entry))
+ {
+ ret_val = g_strdup (GDATA_DOCUMENTS_SPREADSHEET_ODS);
+ }
+ else if (GDATA_IS_DOCUMENTS_TEXT (entry))
+ {
+ ret_val = g_strdup (GDATA_DOCUMENTS_TEXT_ODT);
+ }
+ else
+ {
+ GList *categories;
+ GList *l;
+ const gchar *content_type = NULL;
+
+ categories = gdata_entry_get_categories (entry);
+ for (l = categories; l != NULL; l = l->next)
+ {
+ GDataCategory *category = GDATA_CATEGORY (l->data);
+ const gchar *scheme;
+ const gchar *term;
+
+ scheme = gdata_category_get_scheme (category);
+ term = gdata_category_get_term (category);
+ if (g_strcmp0 (scheme, CATEGORY_SCHEMA_KIND) == 0 &&
+ g_strcmp0 (term, CATEGORY_SCHEMA_KIND_FILE) == 0)
+ {
+ content_type = gdata_category_get_label (category);
+ break;
+ }
+ }
+
+ if (g_strcmp0 (content_type, "image/jpeg") == 0)
+ ret_val = g_strdup ("jpeg");
+ else if (g_strcmp0 (content_type, "image/png") == 0)
+ ret_val = g_strdup ("png");
+ }
+
+ out:
+ 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;
+ gboolean is_folder;
+ gboolean is_root;
+ const gchar *etag;
+ const gchar *id;
+ const gchar *name;
+ const gchar *title;
+ gchar *content_type = NULL;
+ gint64 atime;
+ gint64 ctime;
+ gint64 mtime;
+
+ 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_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;
+ }
+
+ if (is_symlink)
+ {
+ file_type = G_FILE_TYPE_SYMBOLIC_LINK;
+ g_file_info_set_is_symlink (info, TRUE);
+ g_file_info_set_symlink_target (info, filename);
+ }
+
+ if (content_type != NULL)
+ {
+ GIcon *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);
+
+ 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;
+
+ if (is_symlink)
+ {
+ name = symlink_name;
+ }
+ else
+ {
+ /* We need the document-id, not id or resource-id, because
+ * gdata_documents_entry_get_path returns a path based on it.
+ */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ name = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (entry));
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+ }
+
+ 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);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, title);
+
+ 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 *email_address;
+ const gchar *name;
+
+ email_address = gdata_author_get_email_address (author);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER, email_address);
+
+ name = gdata_author_get_name (author);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER_REAL, name);
+ }
+
+ etag = gdata_entry_get_etag (entry);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
+
+ id = gdata_entry_get_id (entry);
+ g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE, id);
+
+ out:
+ g_free (content_type);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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 found_new;
+ gboolean succeeded_once = FALSE;
+ guint max_results = MAX_RESULTS_INITIAL;
+
+ /* The paging mechanism based on start-index and
+ * gdata_query_next_page is not working at the moment. If we start
+ * with (1, 50) and then call gdata_query_next_page, we get a feed
+ * for (1, 50) instead of (51, 50). The result is the same if we
+ * create a new query instead of calling gdata_query_next_page.
+ *
+ * Basically, the start-index always stays at 1.
+ *
+ * The only way I could get it to work was to keep increasing the
+ * max-results and keep the start-index at 1.
+ */
+
+ do
+ {
+ GList *entries;
+ GList *l;
+
+ found_new = FALSE;
+
+ query = gdata_documents_query_new_with_limits (NULL, 1, max_results);
+ gdata_documents_query_set_show_folders (query, TRUE);
+
+ 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);
+ }
+
+ goto out;
+ }
+
+ if (!succeeded_once)
+ {
+ g_hash_table_remove_all (self->entries);
+ succeeded_once = TRUE;
+ }
+
+ entries = gdata_feed_get_entries (GDATA_FEED (feed));
+ for (l = entries; l != NULL; l = l->next)
+ {
+ GDataEntry *entry = GDATA_ENTRY (l->data);
+ const gchar *document_id;
+
+ /* We need the document-id, not id or resource-id, because
+ * gdata_documents_entry_get_path returns a path based on it.
+ */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (entry));
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
+ if (g_hash_table_lookup (self->entries, document_id) == NULL)
+ {
+ g_hash_table_insert (self->entries, g_strdup (document_id), g_object_ref (entry));
+ found_new = TRUE;
+ }
+ }
+
+ max_results *= MAX_RESULTS_FACTOR;
+ g_clear_object (&feed);
+ g_clear_object (&query);
+ } while (found_new);
+
+ 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;
+ GInputStream *stream = G_INPUT_STREAM (source_object);
+ GVfsJobCloseRead *job = G_VFS_JOB_CLOSE_READ (user_data);
+
+ error = NULL;
+ 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 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;
+ gchar *document_id = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+
+ document_id = g_path_get_basename (filename);
+ entry = g_hash_table_lookup (self->entries, document_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, document_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 (document_id);
+ 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;
+ const gchar *real_filename;
+ gchar *document_id = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ delete: %s\n", filename);
+
+ real_filename = g_hash_table_lookup (self->lookaside, filename);
+ if (real_filename != NULL)
+ 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;
+ }
+
+ document_id = g_path_get_basename (filename);
+ entry = g_hash_table_lookup (self->entries, document_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, document_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 ();
+
+ 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, document_id);
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_free (document_id);
+ 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;
+ GError *error;
+ GList *entries = NULL;
+ GList *l;
+
+ 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;
+ }
+
+ self->entries_stale = FALSE;
+ }
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ entries = g_hash_table_get_values (self->entries);
+ for (l = entries; l != NULL; l = l->next)
+ {
+ GDataEntry *entry = GDATA_ENTRY (l->data);
+ gchar *parent_path;
+ gchar *path;
+
+ path = gdata_documents_entry_get_path (GDATA_DOCUMENTS_ENTRY (entry));
+ 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_list_free (entries);
+ 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;
+ GDataAuthorizationDomain *auth_domain;
+ GDataDocumentsFolder *folder = NULL;
+ GDataDocumentsFolder *parent = NULL;
+ GDataEntry *new_entry = NULL;
+ GError *error;
+ gboolean is_root;
+ const gchar *document_id;
+ gchar *parent_id = NULL;
+ gchar *parent_path = NULL;
+ gchar *path = NULL;
+ gchar *title = NULL;
+ gchar *upload_uri = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ make_directory: %s\n", 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 create root directory"));
+ goto out;
+ }
+
+ parent_path = g_path_get_dirname (filename);
+ 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;
+ }
+ }
+ }
+
+ upload_uri = gdata_documents_service_get_upload_uri (parent);
+ if (upload_uri == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Upload URI not found"));
+ goto out;
+ }
+
+ folder = gdata_documents_folder_new (NULL);
+ title = g_path_get_basename (filename);
+ gdata_entry_set_title (GDATA_ENTRY (folder), title);
+
+ auth_domain = gdata_documents_service_get_primary_authorization_domain ();
+
+ error = NULL;
+ new_entry = gdata_service_insert_entry (GDATA_SERVICE (self->service),
+ auth_domain,
+ upload_uri,
+ GDATA_ENTRY (folder),
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* We need the document-id, not id or resource-id, because
+ * gdata_documents_entry_get_path returns a path based on it.
+ */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (new_entry));
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
+ g_hash_table_insert (self->entries, g_strdup (document_id), g_object_ref (new_entry));
+
+ path = gdata_documents_entry_get_path (GDATA_DOCUMENTS_ENTRY (new_entry));
+ g_hash_table_insert (self->lookaside, g_strdup (filename), g_strdup (path));
+
+ self->entries_stale = TRUE;
+ //g_hash_table_foreach (self->monitors, emit_create_event, (gpointer) path);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&new_entry);
+ g_clear_object (&folder);
+ g_free (parent_id);
+ g_free (parent_path);
+ g_free (path);
+ g_free (title);
+ g_free (upload_uri);
+ 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);
+ GList *accounts = NULL;
+ GList *l;
+ gboolean account_found = FALSE;
+ const gchar *mount_identity;
+
+ g_debug ("+ mount\n");
+
+ if (self->client == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Failed to connect to GOA"));
+ 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;
+ GDataDownloadStream *stream;
+ GError *error;
+ gboolean is_folder;
+ gboolean is_root;
+ gchar *document_id = NULL;
+ gchar *export_format = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ open_for_read: %s\n", filename);
+
+ document_id = g_path_get_basename (filename);
+ entry = g_hash_table_lookup (self->entries, document_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, document_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;
+ }
+
+ export_format = get_export_format_from_entry (entry);
+ if (export_format == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Not supported"));
+ goto out;
+ }
+
+ error = NULL;
+ stream = gdata_documents_document_download (GDATA_DOCUMENTS_DOCUMENT (entry),
+ self->service,
+ export_format,
+ cancellable,
+ &error);
+ if (error != NULL)
+ {
+ g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
+ g_error_free (error);
+ goto out;
+ }
+
+ 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 (export_format);
+ g_free (document_id);
+ g_debug ("- open_for_read\n");
+ g_rec_mutex_unlock (&self->mutex);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+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 *document_id;
+ const gchar *real_parent_path;
+ const gchar *title;
+ gchar *parent_id = NULL;
+ gchar *parent_path = NULL;
+ gchar *path = NULL;
+ gchar *upload_uri = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ push: %s -> %s\n", local_path, 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 (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;
+ }
+ }
+ }
+
+ upload_uri = gdata_documents_service_get_upload_uri (parent);
+ if (upload_uri == NULL)
+ {
+ g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Upload URI not found"));
+ 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;
+ }
+
+ /* We need the document-id, not id or resource-id, because
+ * gdata_documents_entry_get_path returns a path based on it.
+ */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (new_document));
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
+ g_hash_table_insert (self->entries, g_strdup (document_id), g_object_ref (new_document));
+
+ path = gdata_documents_entry_get_path (GDATA_DOCUMENTS_ENTRY (new_document));
+ g_hash_table_insert (self->lookaside, g_strdup (destination), g_strdup (path));
+
+ self->entries_stale = TRUE;
+ g_hash_table_foreach (self->monitors, emit_create_event, (gpointer) path);
+
+ 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 (upload_uri);
+ 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;
+ const gchar *real_filename;
+ gchar *document_id = NULL;
+ gchar *symlink_name = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ query_info: %s\n", filename);
+
+ real_filename = g_hash_table_lookup (self->lookaside, filename);
+ if (real_filename != NULL)
+ {
+ is_symlink = TRUE;
+ symlink_name = g_path_get_basename (filename);
+ filename = real_filename;
+ }
+
+ document_id = g_path_get_basename (filename);
+ entry = g_hash_table_lookup (self->entries, document_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, document_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 (document_id);
+ 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);
+ 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 = gdata_documents_entry_get_path (GDATA_DOCUMENTS_ENTRY (entry));
+
+ 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 gboolean
+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");
+ 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;
+ gboolean is_root;
+ const gchar *new_document_id;
+ const gchar *real_filename;
+ gchar *document_id = NULL;
+ gchar *path = NULL;
+
+ g_rec_mutex_lock (&self->mutex);
+ g_debug ("+ set_display_name: %s, %s\n", filename, display_name);
+
+ real_filename = g_hash_table_lookup (self->lookaside, filename);
+ if (real_filename != NULL)
+ 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 rename the root directory"));
+ goto out;
+ }
+
+ document_id = g_path_get_basename (filename);
+ entry = g_hash_table_lookup (self->entries, document_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, document_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, document_id);
+
+ /* We need the document-id, not id or resource-id, because
+ * gdata_documents_entry_get_path returns a path based on it.
+ */
+ G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
+ new_document_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (new_entry));
+ G_GNUC_END_IGNORE_DEPRECATIONS;
+
+ g_hash_table_insert (self->entries, g_strdup (new_document_id), g_object_ref (new_entry));
+
+ path = gdata_documents_entry_get_path (GDATA_DOCUMENTS_ENTRY (new_entry));
+ g_vfs_job_set_display_name_set_new_path (job, path);
+
+ g_warn_if_fail (g_strcmp0 (filename, path) == 0);
+
+ g_vfs_job_succeeded (G_VFS_JOB (job));
+
+ out:
+ g_clear_object (&new_entry);
+ g_free (document_id);
+ g_free (path);
+ 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->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->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->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_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)
+{
+ GError *error;
+
+ 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;
+
+ error = NULL;
+ self->client = get_goa_client_sync (&error);
+ if (self->client == NULL)
+ {
+ g_warning ("Failed to connect to GOA: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+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]