mirror of https://gitee.com/openkylin/gvfs.git
4272 lines
150 KiB
C
4272 lines
150 KiB
C
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||
/* gvfs - extensions for gio
|
||
*
|
||
* Copyright (C) 2009 Thibault Saunier
|
||
* Copyright (C) 2014, 2015 Red Hat, Inc.
|
||
*
|
||
* This library is free software; you can redistribute it and/or
|
||
* modify it under the terms of the GNU Lesser General Public
|
||
* License as published by the Free Software Foundation; either
|
||
* version 2 of the License, or (at your option) any later version.
|
||
*
|
||
* This library is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
* Lesser General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU Lesser General
|
||
* Public License along with this library; if not, write to the
|
||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||
* Boston, MA 02110-1301, USA.
|
||
*
|
||
* Author: Debarshi Ray <debarshir@gnome.org>
|
||
* Thibault Saunier <saunierthibault@gmail.com>
|
||
*/
|
||
|
||
#include <config.h>
|
||
|
||
#include <string.h>
|
||
|
||
#include <glib.h>
|
||
#include <glib/gi18n.h>
|
||
#include <gio/gio.h>
|
||
|
||
#define GOA_API_IS_SUBJECT_TO_CHANGE
|
||
#include <gdata/gdata.h>
|
||
#include <goa/goa.h>
|
||
|
||
#include "gvfsbackendgoogle.h"
|
||
#include "gvfsicon.h"
|
||
#include "gvfsjobcloseread.h"
|
||
#include "gvfsjobcopy.h"
|
||
#include "gvfsjobcreatemonitor.h"
|
||
#include "gvfsjobenumerate.h"
|
||
#include "gvfsjobopenforread.h"
|
||
#include "gvfsjobopenforwrite.h"
|
||
#include "gvfsjobqueryfsinfo.h"
|
||
#include "gvfsjobread.h"
|
||
#include "gvfsjobseekread.h"
|
||
#include "gvfsjobsetdisplayname.h"
|
||
#include "gvfsjobwrite.h"
|
||
#include "gvfsmonitor.h"
|
||
#include "gvfsdaemonutils.h"
|
||
|
||
struct _GVfsBackendGoogle
|
||
{
|
||
GVfsBackend parent;
|
||
GDataDocumentsService *service;
|
||
GDataEntry *root;
|
||
GDataEntry *home;
|
||
GDataEntry *shared_with_me_dir;
|
||
GDataEntry *shared_drives_dir;
|
||
GHashTable *entries; /* gchar *entry_id -> GDataEntry */
|
||
GHashTable *dir_entries; /* DirEntriesKey -> GDataEntry */
|
||
GHashTable *dir_timestamps; /* gchar *entry_id -> gint64 *timestamp */
|
||
GHashTable *monitors;
|
||
GList *dir_collisions;
|
||
GList *shared_drives;
|
||
GRecMutex mutex; /* guards cache */
|
||
GoaClient *client;
|
||
gchar *account_identity;
|
||
};
|
||
|
||
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 REBUILD_ENTRIES_TIMEOUT 60 /* s */
|
||
|
||
#define URI_PREFIX "https://www.googleapis.com/drive/v2/files/"
|
||
|
||
#define SOURCE_ID_PROPERTY_KEY "GVfsSourceID"
|
||
#define PARENT_ID_PROPERTY_KEY "GVfsParentID"
|
||
|
||
#define ROOT_ID "GVfsRoot"
|
||
#define SHARED_WITH_ME_ID "GVfsSharedWithMe"
|
||
#define SHARED_DRIVES_ID "GVfsSharedDrives"
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
typedef struct
|
||
{
|
||
gchar *title_or_id;
|
||
gchar *parent_id;
|
||
} DirEntriesKey;
|
||
|
||
typedef struct
|
||
{
|
||
GDataEntry *document;
|
||
GDataUploadStream *stream;
|
||
gchar *filename;
|
||
gchar *entry_path;
|
||
} WriteHandle;
|
||
|
||
static GDataEntry *resolve_dir (GVfsBackendGoogle *self,
|
||
const gchar *filename,
|
||
GCancellable *cancellable,
|
||
gchar **out_basename,
|
||
gchar **out_path,
|
||
GError **error);
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static DirEntriesKey *
|
||
dir_entries_key_new (const gchar *title_or_id, const gchar *parent_id)
|
||
{
|
||
DirEntriesKey *k;
|
||
|
||
k = g_slice_new0 (DirEntriesKey);
|
||
k->title_or_id = g_strdup (title_or_id);
|
||
k->parent_id = g_strdup (parent_id);
|
||
return k;
|
||
}
|
||
|
||
static void
|
||
dir_entries_key_free (gpointer data)
|
||
{
|
||
DirEntriesKey *k = (DirEntriesKey *) data;
|
||
|
||
if (k == NULL)
|
||
return;
|
||
|
||
g_free (k->title_or_id);
|
||
g_free (k->parent_id);
|
||
g_slice_free (DirEntriesKey, k);
|
||
}
|
||
|
||
static guint
|
||
entries_in_folder_hash (gconstpointer key)
|
||
{
|
||
DirEntriesKey *k = (DirEntriesKey *) key;
|
||
guint hash1;
|
||
guint hash2;
|
||
|
||
hash1 = g_str_hash (k->title_or_id);
|
||
hash2 = g_str_hash (k->parent_id);
|
||
return hash1 ^ hash2;
|
||
}
|
||
|
||
static gboolean
|
||
entries_in_folder_equal (gconstpointer a, gconstpointer b)
|
||
{
|
||
DirEntriesKey *k_a = (DirEntriesKey *) a;
|
||
DirEntriesKey *k_b = (DirEntriesKey *) b;
|
||
|
||
if (g_strcmp0 (k_a->title_or_id, k_b->title_or_id) == 0 &&
|
||
g_strcmp0 (k_a->parent_id, k_b->parent_id) == 0)
|
||
return TRUE;
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
log_dir_entries (GVfsBackendGoogle *self)
|
||
{
|
||
GHashTableIter iter;
|
||
GDataEntry *entry;
|
||
DirEntriesKey *key;
|
||
GList *l;
|
||
|
||
if (!g_getenv ("GVFS_GOOGLE_DEBUG"))
|
||
return;
|
||
|
||
g_hash_table_iter_init (&iter, self->dir_entries);
|
||
while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &entry))
|
||
{
|
||
g_debug (" Real ID = %s, (%s, %s) -> %p, %d\n",
|
||
gdata_entry_get_id (GDATA_ENTRY (entry)),
|
||
key->title_or_id,
|
||
key->parent_id,
|
||
entry,
|
||
((GObject *) entry)->ref_count);
|
||
}
|
||
|
||
for (l = self->dir_collisions; l != NULL; l = l->next)
|
||
{
|
||
entry = GDATA_ENTRY (l->data);
|
||
g_debug ("* Real ID = %s, (%s) -> %p, %d\n",
|
||
gdata_entry_get_id (entry),
|
||
gdata_entry_get_title (entry),
|
||
entry,
|
||
((GObject *) entry)->ref_count);
|
||
}
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static WriteHandle *
|
||
write_handle_new (GDataEntry *document, GDataUploadStream *stream, const gchar *filename, const gchar *entry_path)
|
||
{
|
||
WriteHandle *handle;
|
||
|
||
handle = g_slice_new0 (WriteHandle);
|
||
|
||
if (document != NULL)
|
||
handle->document = g_object_ref (document);
|
||
|
||
if (stream != NULL)
|
||
{
|
||
handle->stream = g_object_ref (stream);
|
||
if (handle->document == NULL)
|
||
handle->document = g_object_ref (gdata_upload_stream_get_entry (stream));
|
||
}
|
||
|
||
handle->filename = g_strdup (filename);
|
||
handle->entry_path = g_strdup (entry_path);
|
||
|
||
return handle;
|
||
}
|
||
|
||
static void
|
||
write_handle_free (gpointer data)
|
||
{
|
||
WriteHandle *handle = (WriteHandle *) data;
|
||
|
||
if (handle == NULL)
|
||
return;
|
||
|
||
g_clear_object (&handle->document);
|
||
g_clear_object (&handle->stream);
|
||
g_free (handle->filename);
|
||
g_free (handle->entry_path);
|
||
g_slice_free (WriteHandle, handle);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
sanitize_error (GError **error)
|
||
{
|
||
if (g_error_matches (*error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED) ||
|
||
g_error_matches (*error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_FORBIDDEN))
|
||
{
|
||
g_warning ("%s", (*error)->message);
|
||
g_clear_error (error);
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, _("Permission denied"));
|
||
}
|
||
else if (g_error_matches (*error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_NOT_FOUND))
|
||
{
|
||
g_warning ("%s", (*error)->message);
|
||
g_clear_error (error);
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("Target object doesn’t exist"));
|
||
}
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
emit_event_internal (GVfsMonitor *monitor,
|
||
const gchar *entry_path,
|
||
GFileMonitorEvent event)
|
||
{
|
||
const gchar *monitored_path;
|
||
gchar *parent_path;
|
||
|
||
if (entry_path == NULL)
|
||
return;
|
||
|
||
monitored_path = g_object_get_data (G_OBJECT (monitor), "g-vfs-backend-google-path");
|
||
parent_path = g_path_get_dirname (entry_path);
|
||
|
||
if (g_strcmp0 (parent_path, monitored_path) == 0)
|
||
{
|
||
g_debug (" emit event %d on parent directory for %s\n", event, entry_path);
|
||
g_vfs_monitor_emit_event (monitor, event, entry_path, NULL);
|
||
}
|
||
else if (g_strcmp0 (entry_path, monitored_path) == 0)
|
||
{
|
||
g_debug (" emit event %d on file %s\n", event, entry_path);
|
||
g_vfs_monitor_emit_event (monitor, event, entry_path, NULL);
|
||
}
|
||
|
||
g_free (parent_path);
|
||
}
|
||
|
||
static void
|
||
emit_attribute_changed_event (gpointer monitor,
|
||
gpointer unused,
|
||
gpointer entry_path)
|
||
{
|
||
emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED);
|
||
}
|
||
|
||
static void
|
||
emit_changed_event (gpointer monitor,
|
||
gpointer unused,
|
||
gpointer entry_path)
|
||
{
|
||
emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_CHANGED);
|
||
}
|
||
|
||
static void
|
||
emit_changes_done_event (gpointer monitor,
|
||
gpointer unused,
|
||
gpointer entry_path)
|
||
{
|
||
emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT);
|
||
}
|
||
|
||
static void
|
||
emit_create_event (gpointer monitor,
|
||
gpointer unused,
|
||
gpointer entry_path)
|
||
{
|
||
emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_CREATED);
|
||
}
|
||
|
||
static void
|
||
emit_delete_event (gpointer monitor,
|
||
gpointer unused,
|
||
gpointer entry_path)
|
||
{
|
||
emit_event_internal (G_VFS_MONITOR (monitor), entry_path, G_FILE_MONITOR_EVENT_DELETED);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static gchar *
|
||
get_content_type_from_entry (GDataEntry *entry)
|
||
{
|
||
GList *categories;
|
||
GList *l;
|
||
gchar *ret_val = NULL;
|
||
|
||
categories = gdata_entry_get_categories (entry);
|
||
for (l = categories; l != NULL; l = l->next)
|
||
{
|
||
GDataCategory *category = GDATA_CATEGORY (l->data);
|
||
const gchar *scheme;
|
||
|
||
scheme = gdata_category_get_scheme (category);
|
||
if (g_strcmp0 (scheme, CATEGORY_SCHEMA_KIND) == 0)
|
||
{
|
||
ret_val = g_strdup (gdata_category_get_label (category));
|
||
break;
|
||
}
|
||
}
|
||
|
||
return ret_val;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static GList *
|
||
get_parent_ids (GVfsBackendGoogle *self,
|
||
GDataEntry *entry)
|
||
{
|
||
GList *l;
|
||
GList *links = NULL;
|
||
GList *ids = NULL;
|
||
|
||
links = gdata_entry_look_up_links (entry, GDATA_LINK_PARENT);
|
||
for (l = links; l != NULL; l = l->next)
|
||
{
|
||
GDataLink *link = GDATA_LINK (l->data);
|
||
const gchar *uri;
|
||
|
||
/* HACK: GDataLink does not have the ID, only the URI. Extract
|
||
* the ID from the GDataLink:uri by removing the prefix. Ignore
|
||
* links which don't have the prefix.
|
||
*/
|
||
uri = gdata_link_get_uri (link);
|
||
if (g_str_has_prefix (uri, URI_PREFIX))
|
||
{
|
||
const gchar *id;
|
||
gsize uri_prefix_len;
|
||
|
||
uri_prefix_len = strlen (URI_PREFIX);
|
||
id = uri + uri_prefix_len;
|
||
if (id[0] != '\0')
|
||
{
|
||
ids = g_list_prepend (ids, g_strdup (id));
|
||
}
|
||
}
|
||
}
|
||
|
||
g_list_free (links);
|
||
return ids;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static gint64
|
||
count_files_in_directory_with_title (GVfsBackendGoogle *self,
|
||
const gchar *entry_title,
|
||
GDataEntry *directory)
|
||
{
|
||
gint64 num_same_title_files = 0;
|
||
GHashTableIter iter;
|
||
GDataEntry *entry;
|
||
DirEntriesKey *key;
|
||
|
||
g_hash_table_iter_init (&iter, self->dir_entries);
|
||
while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &entry))
|
||
{
|
||
if (g_strcmp0 (entry_title, gdata_entry_get_title (entry)) == 0 &&
|
||
g_strcmp0 (key->parent_id, gdata_entry_get_id (GDATA_ENTRY (directory))) == 0 &&
|
||
g_strcmp0 (key->title_or_id, gdata_entry_get_id (entry)) == 0)
|
||
num_same_title_files++;
|
||
}
|
||
|
||
return num_same_title_files;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static gboolean
|
||
is_owner (GVfsBackendGoogle *self, GDataEntry *entry)
|
||
{
|
||
GList *l;
|
||
GDataGoaAuthorizer *goa_authorizer;
|
||
GoaAccount *account;
|
||
const gchar *account_identity;
|
||
|
||
goa_authorizer = GDATA_GOA_AUTHORIZER (gdata_service_get_authorizer (GDATA_SERVICE (self->service)));
|
||
account = goa_object_peek_account (gdata_goa_authorizer_get_goa_object (goa_authorizer));
|
||
account_identity = goa_account_get_identity (account);
|
||
|
||
for (l = gdata_entry_get_authors (entry); l != NULL; l = l->next)
|
||
{
|
||
GDataAuthor *author = GDATA_AUTHOR (l->data);
|
||
|
||
if (g_strcmp0 (gdata_author_get_email_address (author), account_identity) == 0) {
|
||
return TRUE;
|
||
}
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static gboolean
|
||
is_shared_with_me (GDataEntry *entry)
|
||
{
|
||
return gdata_documents_entry_get_shared_with_me_date (GDATA_DOCUMENTS_ENTRY (entry)) > 0;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static const gchar *
|
||
_gdata_documents_entry_get_property_value (GDataDocumentsEntry *entry,
|
||
const gchar *key)
|
||
{
|
||
GList *l = NULL;
|
||
GList *document_properties;
|
||
GDataDocumentsProperty *property;
|
||
|
||
property = gdata_documents_property_new (key);
|
||
gdata_documents_property_set_visibility (property, GDATA_DOCUMENTS_PROPERTY_VISIBILITY_PRIVATE);
|
||
|
||
document_properties = gdata_documents_entry_get_document_properties (GDATA_DOCUMENTS_ENTRY (entry));
|
||
l = g_list_find_custom (document_properties, property, (GCompareFunc) gdata_comparable_compare);
|
||
|
||
g_clear_object (&property);
|
||
if (l && l->data)
|
||
return gdata_documents_property_get_value (GDATA_DOCUMENTS_PROPERTY (l->data));
|
||
else
|
||
return NULL;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
/* Since, we now store both the mappings (title, parent_id) -> GDataEntry
|
||
* and (volatile_entry_id, parent_id) -> GDataEntry in the dir_entries hash
|
||
* table, so a case may arise that some (entry_id, parent_id) matches some
|
||
* other entry's (volatile_entry_id, parent_id) tuple. This means that we have
|
||
* a collision on the volatile entry with some other entry's actual ID.
|
||
*
|
||
* The below helper function checks if dir_entries contains a value
|
||
* corresponding to key specified by argument k. If such a value exists, it
|
||
* then checks if both the entries returned have the same ID or not. In case
|
||
* that they have same ID, both entries are guaranteed to be same whereas if the
|
||
* two IDs differ, it means that a collision has ocurred. In this case, we return
|
||
* the ID of the volatile_entry.
|
||
*
|
||
* Semantically, we may have returned a boolean value, but in the case of a
|
||
* collision, we need the volatile entry's ID to log meaningful debug output.
|
||
* So, we decide to return string or NULL. */
|
||
static const gchar *
|
||
get_volatile_entry_id (GVfsBackendGoogle *self, GDataEntry *entry, DirEntriesKey *k)
|
||
{
|
||
GDataEntry *child = NULL;
|
||
const gchar *child_id;
|
||
|
||
if ((child = g_hash_table_lookup (self->dir_entries, k)) != NULL)
|
||
{
|
||
child_id = gdata_entry_get_id (child);
|
||
|
||
/* Be sure that we are not matching title/id of volatile file. This
|
||
* needs to specifically be taken care of while having files with
|
||
* multiple titles, otherwise we may remove an entry which belonged
|
||
* to some other file and errorneously decrement the ref count of
|
||
* that entry (causing seg-faults later). */
|
||
if (g_strcmp0 (gdata_entry_get_id (entry), child_id) == 0)
|
||
return NULL;
|
||
else
|
||
return child_id;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static gboolean
|
||
insert_entry_full (GVfsBackendGoogle *self,
|
||
GDataEntry *entry,
|
||
gboolean track_dir_collisions)
|
||
{
|
||
DirEntriesKey *k;
|
||
GDataEntry *old_entry;
|
||
GDataEntry *volatile_entry = NULL;
|
||
gboolean retval = TRUE;
|
||
gboolean insert_title = TRUE;
|
||
const gchar *id;
|
||
const gchar *old_id;
|
||
const gchar *title;
|
||
const gchar *source_id_property;
|
||
GList *parent_ids, *l, *ll;
|
||
|
||
id = gdata_entry_get_id (entry);
|
||
title = gdata_entry_get_title (entry);
|
||
|
||
g_hash_table_insert (self->entries, g_strdup (id), g_object_ref (entry));
|
||
|
||
source_id_property = _gdata_documents_entry_get_property_value (GDATA_DOCUMENTS_ENTRY (entry),
|
||
SOURCE_ID_PROPERTY_KEY);
|
||
|
||
parent_ids = get_parent_ids (self, entry);
|
||
for (l = parent_ids; l != NULL; l = l->next)
|
||
{
|
||
const gchar *parent_id_property;
|
||
gchar *parent_id = l->data;
|
||
|
||
k = dir_entries_key_new (id, parent_id);
|
||
old_entry = g_hash_table_lookup (self->dir_entries, k);
|
||
|
||
/* Below case happens when I have an entry (say "entry1") which has a GVfsSourceID
|
||
* property (because of a copy operation performed earlier) and the entry
|
||
* corresponding to value of GVfsSourceID property (say "entry2") is moved to the same
|
||
* parent (i.e. the parent of "entry1"), then the entries will collide on
|
||
* the title as well as there will be a contention for inserting real ID
|
||
* of "entry2" vs volatile entry of "entry1".
|
||
*
|
||
* So, we give priority to real ID of "entry1" to maintain the condition that each
|
||
* entry's real ID must be in dir_entries hash table. */
|
||
if (old_entry != NULL && g_strcmp0 (gdata_entry_get_id (old_entry), id) != 0)
|
||
{
|
||
if ((ll = g_list_find (self->dir_collisions, old_entry)) == NULL)
|
||
{
|
||
self->dir_collisions = g_list_prepend (self->dir_collisions, g_object_ref (old_entry));
|
||
g_debug (" insert_entry: Ejected volatile (colliding real) (%s, (%s, %s)) -> %p, %d\n", id, k->title_or_id, k->parent_id, old_entry, ((GObject *)old_entry)->ref_count);
|
||
}
|
||
}
|
||
|
||
g_hash_table_insert (self->dir_entries, k, g_object_ref (entry));
|
||
g_debug (" insert_entry: Inserted real (%s, %s) -> %p\n", id, parent_id, entry);
|
||
|
||
k = dir_entries_key_new (title, parent_id);
|
||
old_entry = g_hash_table_lookup (self->dir_entries, k);
|
||
if (old_entry != NULL)
|
||
{
|
||
old_id = gdata_entry_get_id (old_entry);
|
||
g_debug (" title collision of %s with: (%s, %s) -> %p\n", id, title, parent_id, old_entry);
|
||
if (g_strcmp0 (old_id, title) == 0)
|
||
{
|
||
insert_title = FALSE;
|
||
}
|
||
else
|
||
{
|
||
/* If the collision is not due to the title matching the ID
|
||
* of an earlier GDataEntry, then it is due to duplicate
|
||
* titles.
|
||
*/
|
||
if (g_strcmp0 (old_id, id) < 0)
|
||
insert_title = FALSE;
|
||
}
|
||
}
|
||
|
||
if (insert_title)
|
||
{
|
||
if (old_entry != NULL && track_dir_collisions)
|
||
{
|
||
if ((ll = g_list_find (self->dir_collisions, old_entry)) == NULL)
|
||
{
|
||
self->dir_collisions = g_list_prepend (self->dir_collisions, g_object_ref (old_entry));
|
||
|
||
g_debug (" insert_entry: Ejected title (%s, (%s, %s)) -> %p, %d\n", old_id, title, parent_id, old_entry, ((GObject *)old_entry)->ref_count);
|
||
}
|
||
}
|
||
|
||
g_hash_table_insert (self->dir_entries, k, g_object_ref (entry));
|
||
g_debug (" insert_entry: Inserted title (%s, %s) -> %p\n", title, parent_id, entry);
|
||
}
|
||
else
|
||
{
|
||
if (track_dir_collisions)
|
||
{
|
||
if ((ll = g_list_find (self->dir_collisions, entry)) == NULL)
|
||
{
|
||
self->dir_collisions = g_list_prepend (self->dir_collisions, g_object_ref (entry));
|
||
g_debug (" insert_entry: Skipped title (%s, %s, %s) -> %p, %d\n", id, title, parent_id, entry, ((GObject *)entry)->ref_count);
|
||
}
|
||
}
|
||
|
||
dir_entries_key_free (k);
|
||
}
|
||
|
||
parent_id_property = _gdata_documents_entry_get_property_value (GDATA_DOCUMENTS_ENTRY (entry),
|
||
PARENT_ID_PROPERTY_KEY);
|
||
|
||
if (source_id_property && (g_strcmp0 (parent_id_property, parent_id) == 0))
|
||
{
|
||
k = dir_entries_key_new (source_id_property, parent_id);
|
||
volatile_entry = g_hash_table_lookup (self->dir_entries, k);
|
||
|
||
if (volatile_entry == NULL)
|
||
{
|
||
g_hash_table_insert (self->dir_entries, k, g_object_ref (entry));
|
||
g_debug (" insert_entry: Inserted volatile (%s, %s) -> %p\n", source_id_property, parent_id, entry);
|
||
}
|
||
else
|
||
{
|
||
/* This case only occurs in the following scenario:
|
||
* Suppose I have a file with title "t1" and ID "id1". Suppose we
|
||
* wish to copy this file "t1" over to some folder "folder1".
|
||
* Now, firstly I touch (create) a new file in "folder1" and I
|
||
* set the title of this new file to "id1". So, this file has its
|
||
* ID (say "id2") and title "id1".
|
||
*
|
||
* Now, we copy the file "id1" into "folder1". When we're trying
|
||
* to do so, the volatile path entry which needs to be inserted
|
||
* consists of mapping `("id1", "folder1_ID") -> GDataEntry` but
|
||
* since we had already created the file with title "id1" inside
|
||
* of "folder1", we get a collision of a volatile entry with some
|
||
* file's actual title. If we ignore this case here, the code
|
||
* will definitely work and the copy operation will succeed but
|
||
* when `query_info` is performed on the entry
|
||
* `("id1", "folder1_ID") -> GDataEntry`, we will get the older
|
||
* file and not the newer one (which nautilus tries to find). So,
|
||
* we treat this case just like that of a title collision.
|
||
*
|
||
* This case shouldn't normally happen since it entails that file
|
||
* names be completely gibberish (like IDs), but for the sake of
|
||
* completeness, we handle it here. */
|
||
if (track_dir_collisions)
|
||
{
|
||
/* The below string comparison is used to check if the
|
||
* (id, parent_id) tuple corresponds to some other entry's real ID or
|
||
* of volatile entry. It may so happen that some file which is to
|
||
* be moved/copied will have a volatile entry kept inside
|
||
* GVfsSourceID GDataDocumentsProperty, and that this ID equals
|
||
* to some other file's real ID. The highest precedence is given
|
||
* to real ID, hence we put the volatile entry (here, `id`) into
|
||
* the dir_collisions list. The inherent knowledge here is that
|
||
* two different entries can never have same real ID. */
|
||
if (g_strcmp0 (k->title_or_id, gdata_entry_get_id (volatile_entry)) == 0)
|
||
{
|
||
if ((ll = g_list_find (self->dir_collisions, entry)) == NULL)
|
||
{
|
||
self->dir_collisions = g_list_prepend (self->dir_collisions, g_object_ref (entry));
|
||
g_debug (" insert_entry: Inserted volatile to dir_collisions (%s, (%s, %s)) -> %p, %d\n",
|
||
gdata_entry_get_id (volatile_entry),
|
||
source_id_property,
|
||
parent_id,
|
||
volatile_entry,
|
||
((GObject *)entry)->ref_count);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* (id, parent_id) tuple maps to some other entry (here volatile_entry's)
|
||
* real ID. So, we prepend `entry` to dir_collisions instead of
|
||
* ejecting volatile_entry. */
|
||
|
||
if ((ll = g_list_find (self->dir_collisions, entry)) == NULL)
|
||
{
|
||
self->dir_collisions = g_list_prepend (self->dir_collisions, g_object_ref (volatile_entry));
|
||
g_debug (" insert_entry: Ejected volatile (%s, (%s, %s)) -> %p, %d\n",
|
||
gdata_entry_get_id (volatile_entry),
|
||
source_id_property,
|
||
parent_id,
|
||
volatile_entry,
|
||
((GObject *)volatile_entry)->ref_count);
|
||
}
|
||
|
||
g_hash_table_insert (self->dir_entries, k, g_object_ref (entry));
|
||
g_debug (" insert_entry: Inserted volatile (%s, %s) -> %p, %d\n", source_id_property, parent_id, entry, ((GObject *)entry)->ref_count);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
retval &= insert_title && (!source_id_property || !volatile_entry);
|
||
}
|
||
g_list_free_full (parent_ids, g_free);
|
||
|
||
return retval;
|
||
}
|
||
|
||
static void
|
||
insert_entry (GVfsBackendGoogle *self,
|
||
GDataEntry *entry)
|
||
{
|
||
gint64 *timestamp;
|
||
|
||
timestamp = g_new (gint64, 1);
|
||
*timestamp = g_get_real_time ();
|
||
g_object_set_data_full (G_OBJECT (entry), "timestamp", timestamp, g_free);
|
||
|
||
insert_entry_full (self, entry, TRUE);
|
||
}
|
||
|
||
static void
|
||
insert_custom_entry (GVfsBackendGoogle *self,
|
||
GDataEntry *entry,
|
||
const gchar *parent_id)
|
||
{
|
||
DirEntriesKey *k;
|
||
const gchar *id;
|
||
const gchar *title;
|
||
|
||
id = gdata_entry_get_id (entry);
|
||
title = gdata_entry_get_title (entry);
|
||
|
||
g_hash_table_insert (self->entries, g_strdup (id), g_object_ref (entry));
|
||
|
||
k = dir_entries_key_new (id, parent_id);
|
||
g_hash_table_insert (self->dir_entries, k, g_object_ref (entry));
|
||
|
||
k = dir_entries_key_new (title, parent_id);
|
||
g_hash_table_insert (self->dir_entries, k, g_object_ref (entry));
|
||
}
|
||
|
||
static void
|
||
remove_entry_full (GVfsBackendGoogle *self,
|
||
GDataEntry *entry,
|
||
gboolean restore_dir_collisions)
|
||
{
|
||
DirEntriesKey *k;
|
||
GList *l;
|
||
const gchar *id;
|
||
const gchar *title;
|
||
GList *parent_ids, *ll;
|
||
|
||
id = gdata_entry_get_id (entry);
|
||
title = gdata_entry_get_title (entry);
|
||
|
||
g_hash_table_remove (self->entries, id);
|
||
|
||
if (is_shared_with_me (entry))
|
||
g_hash_table_remove (self->dir_timestamps, SHARED_WITH_ME_ID);
|
||
|
||
parent_ids = get_parent_ids (self, entry);
|
||
for (ll = parent_ids; ll != NULL; ll = ll->next)
|
||
{
|
||
const gchar *source_id_property = NULL;
|
||
const gchar *parent_id_property = NULL;
|
||
const gchar *volatile_entry_id = NULL;
|
||
gchar *parent_id = ll->data;
|
||
|
||
source_id_property = _gdata_documents_entry_get_property_value (GDATA_DOCUMENTS_ENTRY (entry),
|
||
SOURCE_ID_PROPERTY_KEY);
|
||
|
||
g_hash_table_remove (self->dir_timestamps, parent_id);
|
||
|
||
k = dir_entries_key_new (id, parent_id);
|
||
if (g_hash_table_remove (self->dir_entries, k))
|
||
g_debug (" remove_entry: Removed real (%s, %s) -> %p\n", id, parent_id, entry);
|
||
dir_entries_key_free (k);
|
||
|
||
k = dir_entries_key_new (title, parent_id);
|
||
|
||
if ((volatile_entry_id = get_volatile_entry_id (self, entry, k)) != NULL)
|
||
g_debug (" skipping removal of %s as it is volatile path for %s\n", id, volatile_entry_id);
|
||
else if (g_hash_table_remove (self->dir_entries, k))
|
||
g_debug (" remove_entry: Removed title (%s, %s) -> %p\n", title, parent_id, entry);
|
||
|
||
dir_entries_key_free (k);
|
||
|
||
parent_id_property = _gdata_documents_entry_get_property_value (GDATA_DOCUMENTS_ENTRY (entry),
|
||
PARENT_ID_PROPERTY_KEY);
|
||
if (source_id_property && (g_strcmp0 (parent_id_property, parent_id) == 0))
|
||
{
|
||
k = dir_entries_key_new (source_id_property, parent_id);
|
||
|
||
if ((volatile_entry_id = get_volatile_entry_id (self, entry, k)) != NULL)
|
||
g_debug (" skipping removal of %s as it is volatile path for %s\n", id, volatile_entry_id);
|
||
else if (g_hash_table_remove (self->dir_entries, k))
|
||
g_debug (" remove_entry: Removed volatile (%s, %s) -> %p\n", source_id_property, parent_id, entry);
|
||
|
||
dir_entries_key_free (k);
|
||
}
|
||
|
||
/* Lets remove the entry from the dir_collisions list as well. It's
|
||
* guaranteed that at a time, only 1 entry will exist in dir_collisions */
|
||
l = g_list_find (self->dir_collisions, entry);
|
||
if (l != NULL)
|
||
{
|
||
self->dir_collisions = g_list_remove_link (self->dir_collisions, l);
|
||
g_object_unref (entry);
|
||
g_list_free (l);
|
||
}
|
||
|
||
if (restore_dir_collisions)
|
||
{
|
||
for (l = self->dir_collisions; l != NULL; l = l->next)
|
||
{
|
||
GDataEntry *colliding_entry = GDATA_ENTRY (l->data);
|
||
|
||
if (insert_entry_full (self, colliding_entry, FALSE))
|
||
{
|
||
self->dir_collisions = g_list_remove_link (self->dir_collisions, l);
|
||
g_debug (" remove_entry: Restored %p\n", colliding_entry);
|
||
g_list_free (l);
|
||
g_object_unref (colliding_entry);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
g_list_free_full (parent_ids, g_free);
|
||
}
|
||
|
||
static void
|
||
remove_entry (GVfsBackendGoogle *self,
|
||
GDataEntry *entry)
|
||
{
|
||
remove_entry_full (self, entry, TRUE);
|
||
}
|
||
|
||
static void
|
||
remove_dir (GVfsBackendGoogle *self,
|
||
GDataEntry *parent)
|
||
{
|
||
GHashTableIter iter;
|
||
GDataEntry *entry;
|
||
gchar *parent_id;
|
||
GList *l;
|
||
|
||
/* g_strdup() is necessary to prevent segfault because gdata_entry_get_id() calls g_free() */
|
||
parent_id = g_strdup (gdata_entry_get_id (parent));
|
||
|
||
g_hash_table_remove (self->dir_timestamps, parent_id);
|
||
|
||
g_hash_table_iter_init (&iter, self->entries);
|
||
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &entry))
|
||
{
|
||
DirEntriesKey *k;
|
||
const gchar *volatile_entry_id;
|
||
|
||
k = dir_entries_key_new (gdata_entry_get_id (entry), parent_id);
|
||
|
||
if (g_hash_table_lookup (self->dir_entries, k) != NULL)
|
||
{
|
||
if ((volatile_entry_id = get_volatile_entry_id (self, entry, k)) != NULL)
|
||
{
|
||
g_debug ("Skipping removal of (%s, %s) as it is volatile path for %s\n",
|
||
k->title_or_id,
|
||
k->parent_id,
|
||
volatile_entry_id);
|
||
dir_entries_key_free (k);
|
||
continue;
|
||
}
|
||
|
||
g_object_ref (entry);
|
||
g_hash_table_iter_remove (&iter);
|
||
remove_entry_full (self, entry, FALSE);
|
||
g_object_unref (entry);
|
||
}
|
||
|
||
dir_entries_key_free (k);
|
||
}
|
||
|
||
for (l = self->dir_collisions; l != NULL; l = l->next)
|
||
{
|
||
GDataEntry *colliding_entry = GDATA_ENTRY (l->data);
|
||
|
||
if (insert_entry_full (self, colliding_entry, FALSE))
|
||
{
|
||
self->dir_collisions = g_list_remove_link (self->dir_collisions, l);
|
||
g_debug (" remove_entry: Restored %p\n", colliding_entry);
|
||
g_list_free (l);
|
||
g_object_unref (colliding_entry);
|
||
}
|
||
}
|
||
|
||
g_free (parent_id);
|
||
}
|
||
|
||
static gboolean
|
||
is_entry_valid (GDataEntry *entry)
|
||
{
|
||
gint64 *timestamp;
|
||
|
||
timestamp = g_object_get_data (G_OBJECT (entry), "timestamp");
|
||
if (timestamp == NULL)
|
||
return TRUE;
|
||
|
||
return (g_get_real_time () - *timestamp < REBUILD_ENTRIES_TIMEOUT * G_USEC_PER_SEC);
|
||
}
|
||
|
||
static gboolean
|
||
is_dir_listing_valid (GVfsBackendGoogle *self, GDataEntry *parent)
|
||
{
|
||
gint64 *timestamp;
|
||
|
||
if (parent == self->root)
|
||
return TRUE;
|
||
|
||
timestamp = g_hash_table_lookup (self->dir_timestamps, gdata_entry_get_id (parent));
|
||
if (timestamp != NULL)
|
||
return (g_get_real_time () - *timestamp < REBUILD_ENTRIES_TIMEOUT * G_USEC_PER_SEC);
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
static void
|
||
rebuild_shared_drives_dir (GVfsBackendGoogle *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
|
||
{
|
||
GDataAuthorizationDomain *auth_domain;
|
||
GList *l;
|
||
gint64 *timestamp;
|
||
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
|
||
remove_dir (self, self->shared_drives_dir);
|
||
|
||
for (l = self->shared_drives; l != NULL; l = l->next)
|
||
{
|
||
GDataDocumentsDrive *drive = GDATA_DOCUMENTS_DRIVE (l->data);
|
||
GDataEntry *entry = NULL;
|
||
|
||
entry = gdata_service_query_single_entry (GDATA_SERVICE (self->service),
|
||
auth_domain,
|
||
gdata_entry_get_id (GDATA_ENTRY (drive)),
|
||
NULL,
|
||
GDATA_TYPE_DOCUMENTS_FOLDER,
|
||
cancellable,
|
||
error);
|
||
if (entry == NULL)
|
||
return;
|
||
|
||
/* Replace "My Drive" title by the real name of the Drive. */
|
||
gdata_entry_set_title (entry, gdata_documents_drive_get_name (drive));
|
||
|
||
insert_custom_entry (self, entry, SHARED_DRIVES_ID);
|
||
|
||
g_object_unref (entry);
|
||
}
|
||
|
||
timestamp = g_new (gint64, 1);
|
||
*timestamp = g_get_real_time ();
|
||
g_hash_table_insert (self->dir_timestamps, SHARED_DRIVES_ID, timestamp);
|
||
}
|
||
|
||
static void
|
||
rebuild_dir (GVfsBackendGoogle *self,
|
||
GDataEntry *parent,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GDataDocumentsFeed *feed = NULL;
|
||
GDataDocumentsQuery *query = NULL;
|
||
gboolean succeeded_once = FALSE;
|
||
gchar *search;
|
||
gchar *parent_id;
|
||
|
||
if (parent == self->shared_drives_dir)
|
||
{
|
||
rebuild_shared_drives_dir (self, cancellable, error);
|
||
return;
|
||
}
|
||
|
||
/* g_strdup() is necessary to prevent segfault because gdata_entry_get_id() calls g_free() */
|
||
parent_id = g_strdup (gdata_entry_get_id (parent));
|
||
|
||
if (parent == self->shared_with_me_dir)
|
||
search = g_strdup ("sharedWithMe");
|
||
else
|
||
search = g_strdup_printf ("'%s' in parents", parent_id);
|
||
query = gdata_documents_query_new_with_limits (search, 1, G_MAXUINT);
|
||
gdata_documents_query_set_show_folders (query, TRUE);
|
||
g_free (search);
|
||
|
||
while (TRUE)
|
||
{
|
||
GError *local_error;
|
||
GList *entries;
|
||
GList *l;
|
||
|
||
local_error = NULL;
|
||
feed = gdata_documents_service_query_documents (self->service, query, cancellable, NULL, NULL, &local_error);
|
||
if (local_error != NULL)
|
||
{
|
||
sanitize_error (&local_error);
|
||
g_propagate_error (error, local_error);
|
||
|
||
goto out;
|
||
}
|
||
|
||
if (!succeeded_once)
|
||
{
|
||
gint64 *timestamp;
|
||
|
||
remove_dir (self, parent);
|
||
|
||
timestamp = g_new (gint64, 1);
|
||
*timestamp = g_get_real_time ();
|
||
g_hash_table_insert (self->dir_timestamps, g_strdup (parent_id), timestamp);
|
||
|
||
succeeded_once = TRUE;
|
||
}
|
||
|
||
entries = gdata_feed_get_entries (GDATA_FEED (feed));
|
||
if (entries == NULL)
|
||
break;
|
||
|
||
for (l = entries; l != NULL; l = l->next)
|
||
{
|
||
GDataEntry *entry = GDATA_ENTRY (l->data);
|
||
insert_entry (self, entry);
|
||
}
|
||
|
||
gdata_query_next_page (GDATA_QUERY (query));
|
||
g_clear_object (&feed);
|
||
}
|
||
|
||
out:
|
||
g_clear_object (&feed);
|
||
g_clear_object (&query);
|
||
g_free (parent_id);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static GDataEntry *
|
||
resolve_child (GVfsBackendGoogle *self,
|
||
GDataEntry *parent,
|
||
const gchar *basename,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
DirEntriesKey *k;
|
||
GDataEntry *entry;
|
||
const gchar *parent_id;
|
||
GError *local_error = NULL;
|
||
gboolean is_shared_with_me_dir = (parent == self->shared_with_me_dir);
|
||
|
||
parent_id = gdata_entry_get_id (parent);
|
||
k = dir_entries_key_new (basename, parent_id);
|
||
|
||
if (is_shared_with_me_dir)
|
||
entry = g_hash_table_lookup (self->entries, basename);
|
||
else
|
||
entry = g_hash_table_lookup (self->dir_entries, k);
|
||
|
||
if ((entry == NULL && !is_dir_listing_valid (self, parent)) ||
|
||
(entry != NULL && !is_entry_valid (entry)))
|
||
{
|
||
rebuild_dir (self, parent, cancellable, &local_error);
|
||
if (local_error != NULL)
|
||
{
|
||
g_propagate_error (error, local_error);
|
||
goto out;
|
||
}
|
||
|
||
if (is_shared_with_me_dir)
|
||
entry = g_hash_table_lookup (self->entries, basename);
|
||
else
|
||
entry = g_hash_table_lookup (self->dir_entries, k);
|
||
}
|
||
|
||
if (entry == NULL)
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, _("No such file or directory"));
|
||
goto out;
|
||
}
|
||
|
||
out:
|
||
dir_entries_key_free (k);
|
||
|
||
return entry;
|
||
}
|
||
|
||
static GDataEntry *
|
||
resolve (GVfsBackendGoogle *self,
|
||
const gchar *filename,
|
||
GCancellable *cancellable,
|
||
gchar **out_path,
|
||
GError **error)
|
||
{
|
||
GDataEntry *parent;
|
||
GDataEntry *ret_val = NULL;
|
||
GError *local_error;
|
||
gchar *basename = NULL;
|
||
|
||
g_assert (filename && filename[0] == '/');
|
||
|
||
if (g_strcmp0 (filename, "/") == 0)
|
||
{
|
||
ret_val = self->root;
|
||
|
||
if (out_path != NULL)
|
||
*out_path = g_strdup ("/");
|
||
|
||
goto out;
|
||
}
|
||
|
||
local_error = NULL;
|
||
parent = resolve_dir (self, filename, cancellable, &basename, out_path, &local_error);
|
||
if (local_error != NULL)
|
||
{
|
||
g_propagate_error (error, local_error);
|
||
goto out;
|
||
}
|
||
|
||
ret_val = resolve_child (self, parent, basename, cancellable, &local_error);
|
||
if (ret_val == NULL)
|
||
{
|
||
/* This fallback provides volatile entries for URIs which was used
|
||
* before My Drive folder was added in the root. */
|
||
if (parent == self->root)
|
||
{
|
||
g_clear_error (&local_error);
|
||
ret_val = resolve_child (self, self->home, basename, cancellable, &local_error);
|
||
if (ret_val != NULL && out_path != NULL)
|
||
{
|
||
gchar *tmp;
|
||
tmp = g_build_path ("/", *out_path, gdata_entry_get_id (self->home), NULL);
|
||
g_free (*out_path);
|
||
*out_path = tmp;
|
||
}
|
||
}
|
||
|
||
if (ret_val == NULL)
|
||
{
|
||
g_propagate_error (error, local_error);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
if (out_path != NULL)
|
||
{
|
||
gchar *tmp;
|
||
tmp = g_build_path ("/", *out_path, gdata_entry_get_id (ret_val), NULL);
|
||
g_free (*out_path);
|
||
*out_path = tmp;
|
||
}
|
||
|
||
out:
|
||
g_free (basename);
|
||
return ret_val;
|
||
}
|
||
|
||
static GDataEntry *
|
||
resolve_dir (GVfsBackendGoogle *self,
|
||
const gchar *filename,
|
||
GCancellable *cancellable,
|
||
gchar **out_basename,
|
||
gchar **out_path,
|
||
GError **error)
|
||
{
|
||
GDataEntry *parent;
|
||
GDataEntry *ret_val = NULL;
|
||
GError *local_error;
|
||
gchar *basename = NULL;
|
||
gchar *parent_path = NULL;
|
||
|
||
basename = g_path_get_basename (filename);
|
||
parent_path = g_path_get_dirname (filename);
|
||
|
||
local_error = NULL;
|
||
parent = resolve (self, parent_path, cancellable, out_path, &local_error);
|
||
if (local_error != NULL)
|
||
{
|
||
g_propagate_error (error, local_error);
|
||
goto out;
|
||
}
|
||
|
||
if (!GDATA_IS_DOCUMENTS_FOLDER (parent))
|
||
{
|
||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, _("The file is not a directory"));
|
||
goto out;
|
||
}
|
||
|
||
if (out_basename != NULL)
|
||
{
|
||
*out_basename = basename;
|
||
basename = NULL;
|
||
}
|
||
|
||
ret_val = parent;
|
||
|
||
out:
|
||
g_free (basename);
|
||
g_free (parent_path);
|
||
return ret_val;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static char *
|
||
get_extension_offset (const char *title)
|
||
{
|
||
gchar *end;
|
||
gchar *end2;
|
||
|
||
end = strrchr (title, '.');
|
||
|
||
if (end != NULL && end != title)
|
||
{
|
||
if (g_strcmp0 (end, ".gz") == 0 ||
|
||
g_strcmp0 (end, ".bz2") == 0 ||
|
||
g_strcmp0 (end, ".sit") == 0 ||
|
||
g_strcmp0 (end, ".zip") == 0 ||
|
||
g_strcmp0 (end, ".Z") == 0)
|
||
{
|
||
end2 = end - 1;
|
||
while (end2 > title && *end2 != '.')
|
||
end2--;
|
||
|
||
if (end2 != title)
|
||
end = end2;
|
||
}
|
||
}
|
||
|
||
return end;
|
||
}
|
||
|
||
static gchar *
|
||
generate_copy_name (GVfsBackendGoogle *self, GDataEntry *entry, const gchar *entry_path)
|
||
{
|
||
GDataEntry *existing_entry;
|
||
GDataEntry *parent;
|
||
const gchar *id;
|
||
const gchar *title;
|
||
gchar *extension = NULL;
|
||
gchar *extension_offset;
|
||
gchar *ret_val = NULL;
|
||
gchar *title_without_extension = NULL;
|
||
|
||
title = gdata_entry_get_title (entry);
|
||
|
||
parent = resolve_dir (self, entry_path, NULL, NULL, NULL, NULL);
|
||
if (parent == NULL)
|
||
goto out;
|
||
|
||
existing_entry = resolve_child (self, parent, title, NULL, NULL);
|
||
if (existing_entry == entry)
|
||
goto out;
|
||
|
||
title_without_extension = g_strdup (title);
|
||
extension_offset = get_extension_offset (title_without_extension);
|
||
if (extension_offset != NULL && extension_offset != title_without_extension)
|
||
{
|
||
extension = g_strdup (extension_offset);
|
||
*extension_offset = '\0';
|
||
}
|
||
|
||
id = gdata_entry_get_id (entry);
|
||
ret_val = g_strdup_printf ("%s - %s%s", title_without_extension, id, (extension == NULL) ? "" : extension);
|
||
|
||
out:
|
||
if (ret_val == NULL)
|
||
ret_val = g_strdup (title);
|
||
g_free (extension);
|
||
g_free (title_without_extension);
|
||
return ret_val;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static gboolean
|
||
is_native_file (GDataEntry *entry)
|
||
{
|
||
gchar *content_type;
|
||
gboolean ret = FALSE;
|
||
|
||
content_type = get_content_type_from_entry (entry);
|
||
if (content_type != NULL && g_str_has_prefix (content_type, CONTENT_TYPE_PREFIX_GOOGLE))
|
||
ret = TRUE;
|
||
|
||
g_free (content_type);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static void
|
||
build_file_info (GVfsBackendGoogle *self,
|
||
GDataEntry *entry,
|
||
GFileQueryInfoFlags flags,
|
||
GFileInfo *info,
|
||
GFileAttributeMatcher *matcher,
|
||
const gchar *filename,
|
||
const gchar *entry_path,
|
||
GError **error)
|
||
{
|
||
GFileType file_type;
|
||
GList *authors;
|
||
gboolean is_folder = FALSE;
|
||
gboolean is_root = FALSE;
|
||
gboolean is_symlink = FALSE;
|
||
const gchar *etag;
|
||
const gchar *id;
|
||
const gchar *name;
|
||
const gchar *title;
|
||
gchar *escaped_name = NULL;
|
||
gchar *content_type = NULL;
|
||
gchar *copy_name = NULL;
|
||
gchar *symlink_name = NULL;
|
||
gint64 atime;
|
||
gint64 ctime;
|
||
gint64 mtime;
|
||
gsize i;
|
||
gboolean is_shared_with_me_dir = (entry == self->shared_with_me_dir);
|
||
gboolean is_home = (entry == self->home);
|
||
gboolean is_shared_drives_dir = (entry == self->shared_drives_dir);
|
||
gboolean can_edit;
|
||
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (entry))
|
||
is_folder = TRUE;
|
||
|
||
if (entry == self->root)
|
||
is_root = TRUE;
|
||
|
||
if (filename != NULL && g_strcmp0 (entry_path, filename) != 0) /* volatile */
|
||
{
|
||
is_symlink = TRUE;
|
||
symlink_name = g_path_get_basename (filename);
|
||
}
|
||
|
||
/* TODO: It is not always possible to rename, delete, or list children.
|
||
* However, the proper implementation of gdata_documents_entry_can_rename/
|
||
* _delete/_list_children would require port to Google Drive API v3. */
|
||
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_RENAME,
|
||
!is_root && !is_home && !is_shared_with_me_dir && !is_shared_drives_dir);
|
||
|
||
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE, is_folder);
|
||
|
||
g_file_info_set_is_symlink (info, is_symlink);
|
||
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VOLATILE, is_symlink);
|
||
|
||
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE);
|
||
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE,
|
||
!is_root && !is_home && !is_shared_with_me_dir && !is_shared_drives_dir);
|
||
|
||
can_edit = gdata_documents_entry_can_edit (GDATA_DOCUMENTS_ENTRY (entry));
|
||
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
|
||
!is_root && !is_shared_with_me_dir && !is_shared_drives_dir && can_edit);
|
||
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ, TRUE);
|
||
|
||
if (is_folder)
|
||
{
|
||
content_type = g_strdup ("inode/directory");
|
||
file_type = G_FILE_TYPE_DIRECTORY;
|
||
}
|
||
else
|
||
{
|
||
content_type = get_content_type_from_entry (entry);
|
||
file_type = G_FILE_TYPE_REGULAR;
|
||
|
||
/* We want native Drive content to open in the browser. */
|
||
if (is_native_file (entry))
|
||
{
|
||
GDataLink *alternate;
|
||
const gchar *uri;
|
||
|
||
file_type = G_FILE_TYPE_SHORTCUT;
|
||
alternate = gdata_entry_look_up_link (entry, GDATA_LINK_ALTERNATE);
|
||
uri = gdata_link_get_uri (alternate);
|
||
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, uri);
|
||
}
|
||
else
|
||
{
|
||
goffset size;
|
||
|
||
size = gdata_documents_entry_get_file_size (GDATA_DOCUMENTS_ENTRY (entry));
|
||
g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE, (guint64) size);
|
||
}
|
||
}
|
||
|
||
if (is_symlink)
|
||
{
|
||
if (flags & G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS)
|
||
{
|
||
g_free (content_type);
|
||
content_type = g_strdup ("inode/symlink");
|
||
file_type = G_FILE_TYPE_SYMBOLIC_LINK;
|
||
}
|
||
|
||
g_file_info_set_symlink_target (info, entry_path);
|
||
}
|
||
|
||
if (content_type != NULL)
|
||
{
|
||
GIcon *icon;
|
||
GIcon *symbolic_icon;
|
||
|
||
g_file_info_set_content_type (info, content_type);
|
||
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_FAST_CONTENT_TYPE, content_type);
|
||
|
||
if (is_home)
|
||
{
|
||
icon = g_themed_icon_new_with_default_fallbacks ("user-home");
|
||
symbolic_icon = g_themed_icon_new_with_default_fallbacks ("user-home-symbolic");
|
||
}
|
||
else if (is_shared_with_me_dir)
|
||
{
|
||
icon = g_themed_icon_new_with_default_fallbacks ("folder-publicshare");
|
||
symbolic_icon = g_themed_icon_new_with_default_fallbacks ("folder-publicshare-symbolic");
|
||
}
|
||
else if (is_shared_drives_dir)
|
||
{
|
||
icon = g_themed_icon_new_with_default_fallbacks ("folder-remote");
|
||
symbolic_icon = g_themed_icon_new_with_default_fallbacks ("folder-remote-symbolic");
|
||
}
|
||
else
|
||
{
|
||
icon = g_content_type_get_icon (content_type);
|
||
symbolic_icon = g_content_type_get_symbolic_icon (content_type);
|
||
}
|
||
|
||
g_file_info_set_icon (info, icon);
|
||
g_file_info_set_symbolic_icon (info, symbolic_icon);
|
||
|
||
g_object_unref (icon);
|
||
g_object_unref (symbolic_icon);
|
||
}
|
||
|
||
g_file_info_set_file_type (info, file_type);
|
||
|
||
id = gdata_entry_get_id (entry);
|
||
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ID_FILE, id);
|
||
|
||
if (is_root)
|
||
name = "/";
|
||
else if (is_symlink)
|
||
name = symlink_name;
|
||
else
|
||
name = id;
|
||
|
||
g_file_info_set_name (info, name);
|
||
|
||
title = gdata_entry_get_title (entry);
|
||
g_file_info_set_display_name (info, title);
|
||
g_file_info_set_edit_name (info, title);
|
||
|
||
if (is_root || is_home || is_shared_with_me_dir || is_shared_drives_dir)
|
||
goto out;
|
||
|
||
copy_name = generate_copy_name (self, entry, entry_path);
|
||
|
||
/* Sanitize copy-name by replacing slashes with dashes. This is
|
||
* what nautilus does (for desktop files).
|
||
*/
|
||
for (i = 0; copy_name[i] != '\0'; i++)
|
||
{
|
||
if (copy_name[i] == '/')
|
||
copy_name[i] = '-';
|
||
}
|
||
|
||
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_COPY_NAME, copy_name);
|
||
|
||
atime = gdata_documents_entry_get_last_viewed (GDATA_DOCUMENTS_ENTRY (entry));
|
||
g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, (guint64) atime);
|
||
|
||
ctime = gdata_entry_get_published (entry);
|
||
g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_CREATED, (guint64) ctime);
|
||
|
||
mtime = gdata_entry_get_updated (entry);
|
||
g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, (guint64) mtime);
|
||
|
||
authors = gdata_entry_get_authors (entry);
|
||
if (authors != NULL)
|
||
{
|
||
GDataAuthor *author = GDATA_AUTHOR (authors->data);
|
||
const gchar *author_name;
|
||
const gchar *email_address;
|
||
|
||
author_name = gdata_author_get_name (author);
|
||
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER_REAL, author_name);
|
||
|
||
email_address = gdata_author_get_email_address (author);
|
||
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_USER, email_address);
|
||
}
|
||
|
||
etag = gdata_entry_get_etag (entry);
|
||
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_ETAG_VALUE, etag);
|
||
|
||
if (!is_folder)
|
||
{
|
||
const gchar *thumbnail_uri;
|
||
|
||
thumbnail_uri = gdata_documents_document_get_thumbnail_uri (GDATA_DOCUMENTS_DOCUMENT (entry));
|
||
if (thumbnail_uri != NULL && thumbnail_uri[0] != '\0')
|
||
{
|
||
GIcon *preview;
|
||
GMountSpec *spec;
|
||
|
||
spec = g_vfs_backend_get_mount_spec (G_VFS_BACKEND (self));
|
||
preview = g_vfs_icon_new (spec, thumbnail_uri);
|
||
g_file_info_set_attribute_object (info, G_FILE_ATTRIBUTE_PREVIEW_ICON, G_OBJECT (preview));
|
||
g_object_unref (preview);
|
||
}
|
||
}
|
||
|
||
out:
|
||
g_free (symlink_name);
|
||
g_free (copy_name);
|
||
g_free (escaped_name);
|
||
g_free (content_type);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
remove_monitor_weak_ref (gpointer monitor,
|
||
gpointer unused,
|
||
gpointer monitors)
|
||
{
|
||
g_object_weak_unref (G_OBJECT (monitor), (GWeakNotify) g_hash_table_remove, monitors);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_copy (GVfsBackend *_self,
|
||
GVfsJobCopy *job,
|
||
const gchar *source,
|
||
const gchar *destination,
|
||
GFileCopyFlags flags,
|
||
GFileProgressCallback progress_callback,
|
||
gpointer progress_callback_data)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataDocumentsEntry *dummy_source_entry = NULL;
|
||
GDataDocumentsEntry *new_entry = NULL;
|
||
GDataEntry *destination_parent;
|
||
GDataEntry *source_parent;
|
||
GDataEntry *existing_entry;
|
||
GDataEntry *source_entry;
|
||
GError *error;
|
||
GType source_entry_type;
|
||
const gchar *etag;
|
||
const gchar *id;
|
||
const gchar *source_parent_id;
|
||
const gchar *destination_parent_id;
|
||
const gchar *summary;
|
||
const gchar *title;
|
||
const gchar *dummy_entry_filename;
|
||
gboolean will_overwrite = FALSE;
|
||
gchar *destination_basename = NULL;
|
||
gchar *entry_path = NULL;
|
||
goffset size;
|
||
gchar *parent_path = NULL;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ copy: %s -> %s, %d\n", source, destination, flags);
|
||
log_dir_entries (self);
|
||
|
||
if (flags & G_FILE_COPY_BACKUP)
|
||
{
|
||
/* Return G_IO_ERROR_NOT_SUPPORTED instead of
|
||
* G_IO_ERROR_CANT_CREATE_BACKUP to proceed with the GIO
|
||
* fallback copy.
|
||
*/
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
error = NULL;
|
||
source_entry = resolve (self, source, cancellable, NULL, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
source_parent = resolve_dir (self, source, cancellable, NULL, NULL, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
destination_parent = resolve_dir (self, destination, cancellable, &destination_basename, &parent_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
if (source_entry == self->root || source_parent == self->root || source_parent == self->shared_drives_dir ||
|
||
destination_parent == self->root || destination_parent == self->shared_with_me_dir ||
|
||
destination_parent == self->shared_drives_dir)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
id = gdata_entry_get_id (source_entry);
|
||
title = gdata_entry_get_title (source_entry);
|
||
source_parent_id = gdata_entry_get_id (source_parent);
|
||
destination_parent_id = gdata_entry_get_id (destination_parent);
|
||
|
||
g_debug (" wants overwrite: %d\n", flags & G_FILE_COPY_OVERWRITE);
|
||
|
||
existing_entry = resolve_child (self, destination_parent, destination_basename, cancellable, NULL);
|
||
if (existing_entry != NULL)
|
||
{
|
||
gint64 num_same_title_files;
|
||
|
||
num_same_title_files = count_files_in_directory_with_title (self,
|
||
destination_basename,
|
||
destination_parent);
|
||
|
||
g_debug (" count of files with title same as destination_basename: %ld\n", num_same_title_files);
|
||
|
||
if (flags & G_FILE_COPY_OVERWRITE)
|
||
{
|
||
if (num_same_title_files > 1)
|
||
{
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_FAILED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
/* If the destination-basename matches that of some other entry, we
|
||
* further check if the files have the same ID or not. If the source
|
||
* file as well as the destination file have same ID, we fail the
|
||
* operation. */
|
||
if (g_strcmp0 (gdata_entry_get_id (existing_entry), gdata_entry_get_id (source_entry)) == 0)
|
||
{
|
||
/* We return G_IO_ERROR_FAILED below instead of
|
||
* G_IO_ERROR_NOT_SUPPORTED because _NOT_SUPPORTED implies that the
|
||
* operation hasn't been implemented yet, and hence GIO takes a
|
||
* fallback to perform the operation. So, for copy operation with
|
||
* overwrite, it uses a (replace + read + write) fallback which in
|
||
* turn overwrites the file even though we don't support overwrites.
|
||
*
|
||
* For a concrete case see this discussion:
|
||
* https://gitlab.gnome.org/GNOME/gvfs/merge_requests/58#note_584083
|
||
*/
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_FAILED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
else if (g_strcmp0 (gdata_entry_get_id (existing_entry), destination_basename) == 0 ||
|
||
g_strcmp0 (gdata_entry_get_title (existing_entry), destination_basename) == 0)
|
||
will_overwrite = TRUE;
|
||
else
|
||
{
|
||
/* Neither the ID nor the title of existing_entry matches the destination_basename.
|
||
* This can only happen when destination_basename corresponds to the volatile entry
|
||
* being pointed to by some other file. We simply ignore it and let the copy
|
||
* operation to happen. */
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* We just check for two conditions here (out of a total of 3 we checked above when
|
||
* G_FILE_COPY_OVERWRITE is specified). The first condition "num_same_title_files > 0"
|
||
* checks if we have multiple files with same destination_name in the destination
|
||
* directory, whereas the second condition specifically checks for the case when we have a
|
||
* file whose ID is same as that of source file (i.e. the same file with multiple
|
||
* parents).
|
||
*
|
||
* In the case our destination_name matches the volatile entry of some other file, we
|
||
* simply ignore it and let the copy operation to take place. */
|
||
if (num_same_title_files > 0 ||
|
||
g_strcmp0 (gdata_entry_get_id (existing_entry), destination_basename) == 0)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
|
||
goto out;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* For the cases like `gio copy id1 ./folder/`, the operation actually resolves to its longer form
|
||
* `gio copy id1 ./folder/id1`. We cater such cases differently and check if another file with the
|
||
* same title as that of file with real ID id1 exists in the destination folder. This check allows
|
||
* us to be as similar to POSIX filesystem operations as possible. This way, we can make
|
||
* title-based copy-overwrite operations to work in nautilus. */
|
||
if (g_strcmp0 (destination_basename, id) == 0)
|
||
{
|
||
existing_entry = resolve_child (self, destination_parent, title, cancellable, NULL);
|
||
if (existing_entry != NULL)
|
||
{
|
||
gint64 num_same_title_files;
|
||
|
||
num_same_title_files = count_files_in_directory_with_title (self, title, destination_parent);
|
||
g_debug (" count of same title files: %ld\n", num_same_title_files);
|
||
|
||
if (flags & G_FILE_COPY_OVERWRITE)
|
||
{
|
||
if (num_same_title_files > 1)
|
||
{
|
||
/* We return G_IO_ERROR_FAILED below instead of
|
||
* G_IO_ERROR_NOT_SUPPORTED because _NOT_SUPPORTED implies that the
|
||
* operation hasn't been implemented yet, and hence GIO takes a
|
||
* fallback to perform the operation. So, for copy operation with
|
||
* overwrite, it uses a (replace + read + write) fallback which in
|
||
* turn overwrites the file even though we don't support overwrites.
|
||
*
|
||
* For a concrete case see this discussion:
|
||
* https://gitlab.gnome.org/GNOME/gvfs/merge_requests/58#note_584083
|
||
*/
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_FAILED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (existing_entry))
|
||
{
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (source_entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_MERGE,
|
||
_("Can’t copy directory over directory"));
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_IS_DIRECTORY,
|
||
_("Can’t copy file over directory"));
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
will_overwrite = (num_same_title_files == 1);
|
||
}
|
||
else
|
||
{
|
||
/* If the title matches the volatile path of some
|
||
* other file (given by existing_entry) and if the titles are
|
||
* different, move operation should be allowed. In that case, we will
|
||
* be having two different entries (each with different titles), but
|
||
* the stored volatile path (in GDataDocumentsProperty) will be same.
|
||
*
|
||
* This isn't an issue since we've already handled those extra
|
||
* entries in insert_entry_full().
|
||
*/
|
||
if (num_same_title_files > 0)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
|
||
goto out;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
g_debug (" will overwrite: %d\n", will_overwrite);
|
||
|
||
/* We again resolve the source_entry after checking existing_entry. This is
|
||
* because when we try to find existing_entry, resolve_child is called which
|
||
* internally calls rebuild_dir function. Now, if between the initial
|
||
* resolution of source_entry and the resolution of existing_entry, the
|
||
* source_entry gets invalidated (possibly due to elapsing of
|
||
* REBUILD_ENTRIES_TIMEOUT seconds), rebuild_dir will update the entry
|
||
* internally in the structures but will free our source_entry. This results
|
||
* in a segfault in the below step.
|
||
*
|
||
* This case was observed when doing the following set of operations:
|
||
* copy --> rename copied file (but don't refresh nautilus) --> copy renamed file
|
||
* in same directory */
|
||
source_entry = resolve (self, source, cancellable, NULL, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (source_entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_RECURSE,
|
||
_("Can’t recursively copy directory"));
|
||
goto out;
|
||
}
|
||
|
||
id = gdata_entry_get_id (source_entry);
|
||
etag = gdata_entry_get_etag (source_entry);
|
||
summary = gdata_entry_get_summary (source_entry);
|
||
title = gdata_entry_get_title (source_entry);
|
||
|
||
source_entry_type = G_OBJECT_TYPE (source_entry);
|
||
|
||
/* When a file is copied to the same folder, Google Drive provides a "Make a
|
||
* copy" option which creates a new file and changes its title from "Foobar.pdf"
|
||
* to "Copy of Foobar". But instead here, nautilus does the heavy-lifting and
|
||
* creates the destination file name as "Foobar (copy).pdf".
|
||
*
|
||
* Moreover, just after copy operation, a query_info operation is performed
|
||
* and it needs the ("Foobar (copy).pdf", destination_parent_id) -> Entry mapping
|
||
* in the cache. Hence, we set the new entry's filename conditionally. */
|
||
if (g_strcmp0 (destination_basename, id) == 0)
|
||
dummy_entry_filename = gdata_entry_get_title (source_entry);
|
||
else
|
||
{
|
||
/* Case occurs when trying to overwrite an existing file using destination_basename
|
||
* set to ID2 in `gio copy ID1 ./folder/ID2`. We need to retain the title of the
|
||
* source file instead of setting dummy_entry_filename to destination_basename. */
|
||
if (will_overwrite && existing_entry &&
|
||
g_strcmp0 (gdata_entry_get_id (existing_entry), destination_basename) == 0)
|
||
dummy_entry_filename = title;
|
||
else
|
||
dummy_entry_filename = destination_basename;
|
||
}
|
||
|
||
dummy_source_entry = g_object_new (source_entry_type,
|
||
"etag", etag,
|
||
"id", id,
|
||
"summary", summary,
|
||
"title", dummy_entry_filename,
|
||
NULL);
|
||
|
||
/*
|
||
* When a file is copied inside the same parent, we don't add any
|
||
* GDataDocumentsProperty on it. This is done so that whenever the new_entry
|
||
* gets inserted into the cache, it doesn't overwrite the mapping
|
||
* (source_id, source_parent) -> (GDataEntry of source)
|
||
* with (source_id, source_parent) -> (GDataEntry of new_entry).
|
||
*
|
||
* Moreover, in this case, just after the copy operation, nautilus performs a
|
||
* query_info searching for the new title of the file (to check whether file
|
||
* has actually been created). Our new_entry already has its title set to
|
||
* destination_basename, hence we don't need to add any extra property here.
|
||
*/
|
||
if (g_strcmp0 (source_parent_id, destination_parent_id) != 0)
|
||
{
|
||
GDataDocumentsProperty *source_id_property, *parent_id_property;
|
||
|
||
source_id_property = gdata_documents_property_new (SOURCE_ID_PROPERTY_KEY);
|
||
gdata_documents_property_set_visibility (source_id_property, GDATA_DOCUMENTS_PROPERTY_VISIBILITY_PRIVATE);
|
||
gdata_documents_property_set_value (source_id_property, id);
|
||
|
||
parent_id_property = gdata_documents_property_new (PARENT_ID_PROPERTY_KEY);
|
||
gdata_documents_property_set_visibility (parent_id_property, GDATA_DOCUMENTS_PROPERTY_VISIBILITY_PRIVATE);
|
||
gdata_documents_property_set_value (parent_id_property, destination_parent_id);
|
||
|
||
gdata_documents_entry_add_documents_property (GDATA_DOCUMENTS_ENTRY (dummy_source_entry), source_id_property);
|
||
gdata_documents_entry_add_documents_property (GDATA_DOCUMENTS_ENTRY (dummy_source_entry), parent_id_property);
|
||
|
||
g_clear_object (&source_id_property);
|
||
g_clear_object (&parent_id_property);
|
||
}
|
||
|
||
error = NULL;
|
||
new_entry = gdata_documents_service_add_entry_to_folder (self->service,
|
||
dummy_source_entry,
|
||
GDATA_DOCUMENTS_FOLDER (destination_parent),
|
||
cancellable,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (GDATA_ENTRY (new_entry)), NULL);
|
||
g_debug (" new entry path: %s\n", entry_path);
|
||
|
||
insert_entry (self, GDATA_ENTRY (new_entry));
|
||
g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
|
||
|
||
if (will_overwrite)
|
||
{
|
||
GDataDocumentsEntry *entry_to_remove = NULL;
|
||
guint parent_ids_len;
|
||
GList *parent_ids;
|
||
gchar *existing_entry_path = NULL;
|
||
|
||
/* The internal ref count has to be increased before removing the
|
||
* existing_entry since remove_entry_full calls g_object_unref() internally */
|
||
g_object_ref (existing_entry);
|
||
remove_entry (self, existing_entry);
|
||
|
||
parent_ids = get_parent_ids (self, existing_entry);
|
||
parent_ids_len = g_list_length (parent_ids);
|
||
if (parent_ids_len > 1 || !is_owner (self, GDATA_ENTRY (existing_entry)))
|
||
{
|
||
/* gdata_documents_service_remove_entry_from_folder () returns the
|
||
* updated entry variable provided as argument with an increased ref.
|
||
* The ref count after the next line shall be 2. */
|
||
entry_to_remove = gdata_documents_service_remove_entry_from_folder (self->service,
|
||
GDATA_DOCUMENTS_ENTRY (existing_entry),
|
||
GDATA_DOCUMENTS_FOLDER (destination_parent),
|
||
cancellable,
|
||
&error);
|
||
}
|
||
else
|
||
{
|
||
GDataAuthorizationDomain *auth_domain;
|
||
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
gdata_service_delete_entry (GDATA_SERVICE (self->service), auth_domain, existing_entry, cancellable, &error);
|
||
}
|
||
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
g_clear_object (&entry_to_remove);
|
||
goto out;
|
||
}
|
||
|
||
existing_entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (existing_entry), NULL);
|
||
g_hash_table_foreach (self->monitors, emit_delete_event, existing_entry_path);
|
||
|
||
g_object_unref (existing_entry);
|
||
g_clear_object (&entry_to_remove);
|
||
}
|
||
|
||
size = gdata_documents_entry_get_file_size (new_entry);
|
||
g_vfs_job_progress_callback (size, size, job);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_clear_object (&dummy_source_entry);
|
||
g_clear_object (&new_entry);
|
||
g_free (destination_basename);
|
||
g_free (entry_path);
|
||
g_free (parent_path);
|
||
log_dir_entries (self);
|
||
g_debug ("- copy\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_move (GVfsBackend *_self,
|
||
GVfsJobMove *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;
|
||
GDataAuthorizationDomain *auth_domain;
|
||
GDataEntry *new_entry = NULL;
|
||
GDataEntry *destination_parent;
|
||
GDataEntry *source_parent;
|
||
GDataEntry *existing_entry;
|
||
GDataEntry *source_entry = NULL;
|
||
GDataLink *source_parent_link = NULL;
|
||
GDataLink *destination_parent_link = NULL;
|
||
GError *error = NULL;
|
||
gboolean will_overwrite = FALSE;
|
||
const gchar *source_id;
|
||
const gchar *source_parent_id;
|
||
const gchar *destination_parent_id;
|
||
const gchar *title;
|
||
gchar *source_parent_link_uri = NULL;
|
||
gchar *destination_parent_link_uri = NULL;
|
||
gchar *destination_basename = NULL;
|
||
gchar *entry_path = NULL;
|
||
gchar *parent_path = NULL;
|
||
goffset size;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ move: %s -> %s, %d\n", source, destination, flags);
|
||
log_dir_entries (self);
|
||
|
||
if (flags & G_FILE_COPY_BACKUP)
|
||
{
|
||
/* Return G_IO_ERROR_NOT_SUPPORTED instead of
|
||
* G_IO_ERROR_CANT_CREATE_BACKUP to proceed with the GIO
|
||
* fallback copy.
|
||
*/
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
source_entry = resolve (self, source, cancellable, NULL, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
source_parent = resolve_dir (self, source, cancellable, NULL, NULL, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
destination_parent = resolve_dir (self, destination, cancellable, &destination_basename, &parent_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
if (source_entry == self->root || source_parent == self->root || source_parent == self->shared_drives_dir ||
|
||
destination_parent == self->root || destination_parent == self->shared_with_me_dir ||
|
||
destination_parent == self->shared_drives_dir)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
source_id = gdata_entry_get_id (source_entry);
|
||
source_parent_id = gdata_entry_get_id (source_parent);
|
||
destination_parent_id = gdata_entry_get_id (destination_parent);
|
||
title = gdata_entry_get_title (source_entry);
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
|
||
g_debug (" wants overwrite: %d\n", flags & G_FILE_COPY_OVERWRITE);
|
||
|
||
existing_entry = resolve_child (self, destination_parent, destination_basename, cancellable, NULL);
|
||
if (existing_entry != NULL)
|
||
{
|
||
const gchar *existing_entry_id;
|
||
gint64 num_same_title_files;
|
||
|
||
existing_entry_id = gdata_entry_get_id (existing_entry);
|
||
num_same_title_files = count_files_in_directory_with_title (self,
|
||
destination_basename,
|
||
destination_parent);
|
||
|
||
g_debug (" count of files with title same as destination_basename: %ld\n", num_same_title_files);
|
||
|
||
/* We don't support overwrites, so we don't need to care
|
||
* about G_IO_ERROR_IS_DIRECTORY and G_IO_ERROR_WOULD_MERGE.
|
||
*
|
||
* Moreover, we don't need to take care of G_IO_ERROR_WOULD_RECURSE
|
||
* too since changing a Folder's parent on Drive is a matter of
|
||
* simply changing "parents" property. We don't have to do any
|
||
* recursive move. */
|
||
if (flags & G_FILE_COPY_OVERWRITE)
|
||
{
|
||
if (num_same_title_files > 1)
|
||
{
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_FAILED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (existing_entry))
|
||
{
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (source_entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_MERGE,
|
||
_("Can’t copy directory over directory"));
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_IS_DIRECTORY,
|
||
_("Can’t copy file over directory"));
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
/* If the destination-basename matches that of some other entry, we
|
||
* further check if the files have the same ID or not. If the source
|
||
* file as well as the destination file have same ID, we fail the
|
||
* operation. */
|
||
if (g_strcmp0 (existing_entry_id, source_id) == 0)
|
||
{
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_FAILED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
else if (g_strcmp0 (existing_entry_id, destination_basename) == 0)
|
||
{
|
||
if (g_strcmp0 (source_parent_id, destination_parent_id) == 0)
|
||
{
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_FAILED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
else
|
||
will_overwrite = TRUE;
|
||
}
|
||
else if (g_strcmp0 (gdata_entry_get_title (existing_entry), destination_basename) == 0)
|
||
will_overwrite = TRUE;
|
||
else
|
||
{
|
||
/* Neither the ID nor the title of existing_entry matches the destination_basename.
|
||
* This can only happen when destination_basename corresponds to the volatile entry
|
||
* being pointed to by some other file. We simply ignore it and let the move
|
||
* operation to happen. */
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* We just check for two conditions here (out of a total of 3 we checked above when
|
||
* G_FILE_COPY_OVERWRITE is specified). The first condition "num_same_title_files > 0"
|
||
* checks if we have multiple files with same destination_name in the destination
|
||
* directory, whereas the second condition specifically checks for the case when we have a
|
||
* file whose ID is same as that of source file (i.e. the same file with multiple
|
||
* parents).
|
||
*
|
||
* In the case our destination_name matches the volatile entry of some other file, we
|
||
* simply ignore it and let the move operation take place. */
|
||
if (num_same_title_files > 0 ||
|
||
g_strcmp0 (gdata_entry_get_id (existing_entry), destination_basename) == 0)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
|
||
goto out;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* For the cases like `gio move id1 ./folder/`, the operation actually resolves to its longer form
|
||
* `gio move id1 ./folder/id1`. We cater such cases differently and check if another file with the
|
||
* same title as that of file with real ID id1 exists in the destination folder. This check allows
|
||
* us to be as similar to POSIX filesystem operations as possible. This way, we can make
|
||
* title-based move-overwrite operations to work in nautilus. */
|
||
if (g_strcmp0 (destination_basename, source_id) == 0)
|
||
{
|
||
existing_entry = resolve_child (self, destination_parent, title, cancellable, NULL);
|
||
if (existing_entry != NULL)
|
||
{
|
||
gint64 num_same_title_files;
|
||
|
||
num_same_title_files = count_files_in_directory_with_title (self,
|
||
title,
|
||
destination_parent);
|
||
|
||
g_debug (" count of same title files: %ld\n", num_same_title_files);
|
||
|
||
if (flags & G_FILE_COPY_OVERWRITE)
|
||
{
|
||
if (num_same_title_files > 1)
|
||
{
|
||
/* We return G_IO_ERROR_FAILED below instead of
|
||
* G_IO_ERROR_NOT_SUPPORTED because _NOT_SUPPORTED implies that the
|
||
* operation hasn't been implemented yet, and hence GIO takes a
|
||
* fallback to perform the operation. So, for copy operation with
|
||
* overwrite, it uses a (replace + read + write) fallback which in
|
||
* turn overwrites the file even though we don't support overwrites.
|
||
*
|
||
* For a concrete case see this discussion:
|
||
* https://gitlab.gnome.org/GNOME/gvfs/merge_requests/58#note_584083
|
||
*/
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_FAILED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (existing_entry))
|
||
{
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (source_entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_MERGE,
|
||
_("Can’t move directory over directory"));
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_IS_DIRECTORY,
|
||
_("Can’t move file over directory"));
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
will_overwrite = (num_same_title_files == 1);
|
||
}
|
||
else
|
||
{
|
||
/* If the title matches the volatile path of some
|
||
* other file (given by existing_entry) and if the titles are
|
||
* different, move operation should be allowed. In that case, we will
|
||
* be having two different entries (each with different titles), but
|
||
* the stored volatile path (in GDataDocumentsProperty) will be same.
|
||
*
|
||
* This isn't an issue since we've already handled those extra
|
||
* entries in insert_entry_full().
|
||
*/
|
||
if (num_same_title_files > 0)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
|
||
goto out;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
g_debug (" will overwrite: %d\n", will_overwrite);
|
||
|
||
/* The internal ref count has to be increased before removing the
|
||
* source_entry since remove_entry_full calls g_object_unref() internally */
|
||
g_object_ref (source_entry);
|
||
remove_entry (self, source_entry);
|
||
|
||
source_parent_link_uri = g_strconcat (URI_PREFIX, source_parent_id, NULL);
|
||
source_parent_link = gdata_link_new (source_parent_link_uri, GDATA_LINK_PARENT);
|
||
|
||
destination_parent_link_uri = g_strconcat (URI_PREFIX, destination_parent_id, NULL);
|
||
destination_parent_link = gdata_link_new (destination_parent_link_uri, GDATA_LINK_PARENT);
|
||
|
||
if (!gdata_entry_remove_link (source_entry, source_parent_link))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error moving file/folder"));
|
||
g_clear_object (&source_parent_link);
|
||
g_object_unref (source_entry);
|
||
goto out;
|
||
}
|
||
|
||
gdata_entry_add_link (source_entry, destination_parent_link);
|
||
|
||
/* Handle the cases like `gio move id1 folder1/Foobar` and `gio move id1 Foobar`
|
||
* where Foobar is the new title of the file to be moved.
|
||
*
|
||
* The catch here is that we can't support the case `gio move id1 folder1/id1`
|
||
* because that conflicts with what nautilus does when doing a cut + paste
|
||
* operation. This is a tradeoff between handling the common case vs handling a
|
||
* rare case. If you explicity wish to move the file and update the title of
|
||
* the file to its ID, do a move + rename operation. */
|
||
if (g_strcmp0 (destination_basename, source_id) != 0)
|
||
{
|
||
/* When we're trying to overwrite a file using its ID, the destination_basename will be set to
|
||
* the ID. Instead, we want to have the title to be same as that of source entry. */
|
||
if (!(will_overwrite && existing_entry &&
|
||
g_strcmp0 (gdata_entry_get_id (existing_entry), destination_basename) == 0))
|
||
gdata_entry_set_title (source_entry, destination_basename);
|
||
}
|
||
|
||
error = NULL;
|
||
new_entry = gdata_service_update_entry (GDATA_SERVICE (self->service),
|
||
auth_domain,
|
||
source_entry,
|
||
cancellable,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
g_object_unref (source_entry);
|
||
goto out;
|
||
}
|
||
|
||
if (will_overwrite)
|
||
{
|
||
GDataDocumentsEntry *entry_to_remove = NULL;
|
||
guint parent_ids_len;
|
||
GList *parent_ids;
|
||
gchar *existing_entry_path = NULL;
|
||
|
||
/* The internal ref count has to be increased before removing the
|
||
* existing_entry since remove_entry_full calls g_object_unref() internally */
|
||
g_object_ref (existing_entry);
|
||
remove_entry (self, existing_entry);
|
||
|
||
parent_ids = get_parent_ids (self, existing_entry);
|
||
parent_ids_len = g_list_length (parent_ids);
|
||
if (parent_ids_len > 1 || !is_owner (self, GDATA_ENTRY (existing_entry)))
|
||
{
|
||
/* gdata_documents_service_remove_entry_from_folder () returns the
|
||
* updated entry variable provided as argument with an increased ref.
|
||
* The ref count after the next line shall be 2. */
|
||
entry_to_remove = gdata_documents_service_remove_entry_from_folder (self->service,
|
||
GDATA_DOCUMENTS_ENTRY (existing_entry),
|
||
GDATA_DOCUMENTS_FOLDER (destination_parent),
|
||
cancellable,
|
||
&error);
|
||
}
|
||
else
|
||
{
|
||
GDataAuthorizationDomain *auth_domain;
|
||
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
gdata_service_delete_entry (GDATA_SERVICE (self->service), auth_domain, existing_entry, cancellable, &error);
|
||
}
|
||
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
g_clear_object (&entry_to_remove);
|
||
goto out;
|
||
}
|
||
|
||
existing_entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (existing_entry), NULL);
|
||
g_hash_table_foreach (self->monitors, emit_delete_event, existing_entry_path);
|
||
|
||
g_object_unref (existing_entry);
|
||
g_clear_object (&entry_to_remove);
|
||
}
|
||
|
||
entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (new_entry), NULL);
|
||
g_debug (" new entry path: %s\n", entry_path);
|
||
|
||
insert_entry (self, new_entry);
|
||
g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
|
||
|
||
size = gdata_documents_entry_get_file_size (GDATA_DOCUMENTS_ENTRY (new_entry));
|
||
g_vfs_job_progress_callback (size, size, job);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
g_object_unref (source_entry);
|
||
|
||
out:
|
||
g_clear_object (&destination_parent_link);
|
||
g_clear_object (&new_entry);
|
||
g_free (source_parent_link_uri);
|
||
g_free (destination_parent_link_uri);
|
||
g_free (destination_basename);
|
||
g_free (entry_path);
|
||
g_free (parent_path);
|
||
log_dir_entries (self);
|
||
g_debug ("- move\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static gboolean
|
||
g_vfs_backend_google_create_dir_monitor (GVfsBackend *_self,
|
||
GVfsJobCreateMonitor *job,
|
||
const gchar *filename,
|
||
GFileMonitorFlags flags)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataEntry *entry;
|
||
GError *error;
|
||
GVfsMonitor *monitor = NULL;
|
||
gchar *entry_path = NULL;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ create_dir_monitor: %s, %d\n", filename, flags);
|
||
|
||
if (flags & G_FILE_MONITOR_SEND_MOVED)
|
||
{
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
error = NULL;
|
||
entry = resolve (self, filename, cancellable, &entry_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_debug (" entry path: %s\n", entry_path);
|
||
|
||
if (!GDATA_IS_DOCUMENTS_FOLDER (entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY, _("The file is not a directory"));
|
||
goto out;
|
||
}
|
||
|
||
monitor = g_vfs_monitor_new (_self);
|
||
g_object_set_data_full (G_OBJECT (monitor), "g-vfs-backend-google-path", g_strdup (entry_path), g_free);
|
||
g_hash_table_add (self->monitors, monitor);
|
||
g_object_weak_ref (G_OBJECT (monitor), (GWeakNotify) g_hash_table_remove, self->monitors);
|
||
g_vfs_job_create_monitor_set_monitor (job, monitor);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_clear_object (&monitor);
|
||
g_free (entry_path);
|
||
g_debug ("- create_dir_monitor\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
return TRUE;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_delete (GVfsBackend *_self,
|
||
GVfsJobDelete *job,
|
||
const gchar *filename)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataEntry *entry;
|
||
GDataEntry *parent;
|
||
GDataDocumentsEntry *new_entry = NULL;
|
||
GError *error;
|
||
gchar *entry_path = NULL;
|
||
GList *parent_ids;
|
||
guint parent_ids_len;
|
||
gchar *id = NULL;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ delete: %s\n", filename);
|
||
|
||
error = NULL;
|
||
entry = resolve (self, filename, cancellable, &entry_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
/* g_strdup() is necessary to prevent segfault because gdata_entry_get_id() calls g_free() */
|
||
id = g_strdup (gdata_entry_get_id (entry));
|
||
|
||
parent = resolve_dir (self, filename, cancellable, NULL, NULL, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
/* The G_IO_ERROR_NOT_EMPTY error is not intentionally returned for folders in
|
||
* Shared with me folder, because the recursive delete would not work, or could
|
||
* really remove the files from the original folder also for the owner... */
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (entry) && parent != self->shared_with_me_dir)
|
||
{
|
||
GHashTableIter iter;
|
||
DirEntriesKey *key;
|
||
|
||
if (!is_dir_listing_valid (self, entry))
|
||
{
|
||
rebuild_dir (self, 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_iter_init (&iter, self->dir_entries);
|
||
while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL))
|
||
{
|
||
if (g_strcmp0 (key->parent_id, id) == 0)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_NOT_EMPTY,
|
||
_("Directory not empty"));
|
||
goto out;
|
||
}
|
||
}
|
||
}
|
||
|
||
g_debug (" entry path: %s\n", entry_path);
|
||
|
||
if (entry == self->root || entry == self->home || entry == self->shared_with_me_dir ||
|
||
entry == self->shared_drives_dir || parent == self->shared_drives_dir)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
/* It has to be removed before the actual call to properly invalidate dir entries. */
|
||
g_object_ref (entry);
|
||
remove_entry (self, entry);
|
||
|
||
error = NULL;
|
||
|
||
parent_ids = get_parent_ids (self, entry);
|
||
parent_ids_len = g_list_length (parent_ids);
|
||
|
||
/* The files in Shared with me folder doesn't have a parent and also we don't
|
||
* have enough permissions to physically delete them. But they can be removed
|
||
* by removal of our permissions... */
|
||
if (parent == self->shared_with_me_dir)
|
||
{
|
||
GDataFeed *acl_feed;
|
||
|
||
acl_feed = gdata_access_handler_get_rules (GDATA_ACCESS_HANDLER (entry),
|
||
GDATA_SERVICE (self->service),
|
||
cancellable,
|
||
NULL, NULL, &error);
|
||
if (error == NULL)
|
||
{
|
||
GDataGoaAuthorizer *goa_authorizer;
|
||
GoaAccount *account;
|
||
const gchar *account_identity;
|
||
GDataAuthorizationDomain *auth_domain;
|
||
GList *entries;
|
||
|
||
goa_authorizer = GDATA_GOA_AUTHORIZER (gdata_service_get_authorizer (GDATA_SERVICE (self->service)));
|
||
account = goa_object_peek_account (gdata_goa_authorizer_get_goa_object (goa_authorizer));
|
||
account_identity = goa_account_get_identity (account);
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
|
||
for (entries = gdata_feed_get_entries (acl_feed); entries != NULL; entries = entries->next)
|
||
{
|
||
const gchar *scope_value = NULL;
|
||
GDataAccessRule *rule = GDATA_ACCESS_RULE (entries->data);
|
||
|
||
gdata_access_rule_get_scope (rule, NULL, &scope_value);
|
||
if (g_strcmp0 (scope_value, account_identity) == 0)
|
||
gdata_service_delete_entry (GDATA_SERVICE (self->service), auth_domain, GDATA_ENTRY (rule), NULL, &error);
|
||
}
|
||
|
||
g_object_unref (acl_feed);
|
||
}
|
||
}
|
||
else if (parent_ids_len > 1 || !is_owner (self, GDATA_ENTRY (entry)))
|
||
{
|
||
/* gdata_documents_service_remove_entry_from_folder () returns the
|
||
* updated entry variable provided as argument with an increased ref.
|
||
* The ref count after the next line shall be 2. */
|
||
new_entry = gdata_documents_service_remove_entry_from_folder (self->service,
|
||
GDATA_DOCUMENTS_ENTRY (entry),
|
||
GDATA_DOCUMENTS_FOLDER (parent),
|
||
cancellable,
|
||
&error);
|
||
}
|
||
else
|
||
{
|
||
GDataAuthorizationDomain *auth_domain;
|
||
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
gdata_service_delete_entry (GDATA_SERVICE (self->service), auth_domain, entry, cancellable, &error);
|
||
}
|
||
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
g_object_unref (entry);
|
||
goto out;
|
||
}
|
||
|
||
/* In case of files owned by somebody else, the new entry is returned
|
||
* even if it had just one parent before the operation. The backend
|
||
* doesn't care about entries without parents (i.e. entries with
|
||
* parent_ids_len = 1), so let's ignore it. */
|
||
if (new_entry && parent_ids_len > 1)
|
||
insert_entry (self, GDATA_ENTRY (new_entry));
|
||
g_hash_table_foreach (self->monitors, emit_delete_event, entry_path);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
g_object_unref (entry);
|
||
|
||
out:
|
||
g_clear_object (&new_entry);
|
||
g_free (entry_path);
|
||
g_free (id);
|
||
g_debug ("- delete\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
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;
|
||
GDataEntry *child;
|
||
GError *error;
|
||
GHashTableIter iter;
|
||
char *parent_path;
|
||
char *id = NULL;
|
||
gboolean is_shared_with_me_dir;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ enumerate: %s\n", filename);
|
||
|
||
error = NULL;
|
||
entry = resolve (self, filename, cancellable, &parent_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
if (!GDATA_IS_DOCUMENTS_FOLDER (entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_DIRECTORY,_("The file is not a directory"));
|
||
goto out;
|
||
}
|
||
|
||
if (!is_dir_listing_valid (self, entry))
|
||
{
|
||
rebuild_dir (self, entry, cancellable, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
/* g_strdup() is necessary to prevent segfault because gdata_entry_get_id() calls g_free() */
|
||
id = g_strdup (gdata_entry_get_id (entry));
|
||
|
||
is_shared_with_me_dir = (entry == self->shared_with_me_dir);
|
||
|
||
g_hash_table_iter_init (&iter, self->entries);
|
||
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &child))
|
||
{
|
||
DirEntriesKey *k;
|
||
gchar *child_id;
|
||
|
||
/* g_strdup() is necessary to prevent segfault because gdata_entry_get_id() calls g_free() */
|
||
child_id = g_strdup (gdata_entry_get_id (child));
|
||
|
||
k = dir_entries_key_new (child_id, id);
|
||
if ((is_shared_with_me_dir && is_shared_with_me (child)) ||
|
||
(!is_shared_with_me_dir && g_hash_table_lookup (self->dir_entries, k) != NULL))
|
||
{
|
||
GFileInfo *info;
|
||
gchar *entry_path;
|
||
gchar *child_filename;
|
||
|
||
/* Be sure that we are not matching title of volatile file */
|
||
if (g_strcmp0 (child_id, gdata_entry_get_id (child)) != 0)
|
||
{
|
||
g_debug ("Skipping %s as it is volatile path for %s\n", child_id, gdata_entry_get_id (child));
|
||
g_free (child_id);
|
||
dir_entries_key_free (k);
|
||
continue;
|
||
}
|
||
|
||
info = g_file_info_new ();
|
||
entry_path = g_build_path ("/", parent_path, child_id, NULL);
|
||
child_filename = g_build_filename (filename, child_id, NULL);
|
||
build_file_info (self, child, flags, info, matcher, child_filename, entry_path, NULL);
|
||
g_vfs_job_enumerate_add_info (job, info);
|
||
g_object_unref (info);
|
||
g_free (entry_path);
|
||
}
|
||
|
||
g_free (child_id);
|
||
dir_entries_key_free (k);
|
||
}
|
||
|
||
g_vfs_job_enumerate_done (job);
|
||
|
||
out:
|
||
g_debug ("- enumerate\n");
|
||
g_free (parent_path);
|
||
g_free (id);
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_make_directory (GVfsBackend *_self,
|
||
GVfsJobMakeDirectory *job,
|
||
const gchar *filename)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataDocumentsEntry *new_folder = NULL;
|
||
GDataDocumentsFolder *folder = NULL;
|
||
GDataEntry *existing_entry;
|
||
GDataEntry *parent;
|
||
GDataEntry *summary_entry;
|
||
GDataEntry *same_id_entry;
|
||
GError *error;
|
||
const gchar *summary;
|
||
gchar *entry_path = NULL;
|
||
gchar *basename = NULL;
|
||
gchar *parent_path = NULL;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ make_directory: %s\n", filename);
|
||
|
||
if (g_strcmp0 (filename, "/") == 0)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
error = NULL;
|
||
parent = resolve_dir (self, filename, cancellable, &basename, &parent_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_debug (" parent path: %s\n", parent_path);
|
||
|
||
summary_entry = g_hash_table_lookup (self->entries, basename);
|
||
if (summary_entry == NULL)
|
||
summary = NULL;
|
||
else
|
||
summary = gdata_entry_get_summary (summary_entry);
|
||
|
||
existing_entry = resolve_child (self, parent, basename, cancellable, NULL);
|
||
if (existing_entry != NULL)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
|
||
goto out;
|
||
}
|
||
|
||
folder = gdata_documents_folder_new (NULL);
|
||
|
||
/* This case is particularly troublesome. Normally, when we call
|
||
* make_directory, the title supplied by the user or client is used to create
|
||
* a file with same title. But strangely, when we're trying to copy a folder
|
||
* over to another directory (i.e. to a different parent), the copy operation
|
||
* would return G_IO_ERROR_WOULD_RECURSE. This means that nautilus will
|
||
* manually create the folders (using make_directory) and copy each file.
|
||
*
|
||
* The major issue here is that nautilus calls make_directory with the title
|
||
* of folder set to ID of the original folder. Suppose, we had a folder "folder1"
|
||
* which needs to be copied. Now, copy operation would return
|
||
* G_IO_ERROR_WOULD_RECURSE (if the parents are different), but nautilus will
|
||
* call make_directory with ID of "folder1" instead of title of "folder1".
|
||
* Furthermore, nautilus calls query_info on the "filename" argument right
|
||
* after it has done make_directory hence we need to create a volatile entry
|
||
* for the folder too.
|
||
*
|
||
* Below fix is hack-ish and will fail if we wish to create a folder
|
||
* whose title equals the ID of any other folder. */
|
||
if ((same_id_entry = g_hash_table_lookup (self->entries, basename)) != NULL)
|
||
{
|
||
GDataDocumentsProperty *source_id_property, *parent_id_property;
|
||
|
||
source_id_property = gdata_documents_property_new (SOURCE_ID_PROPERTY_KEY);
|
||
gdata_documents_property_set_visibility (source_id_property, GDATA_DOCUMENTS_PROPERTY_VISIBILITY_PRIVATE);
|
||
gdata_documents_property_set_value (source_id_property, basename);
|
||
|
||
parent_id_property = gdata_documents_property_new (PARENT_ID_PROPERTY_KEY);
|
||
gdata_documents_property_set_visibility (parent_id_property, GDATA_DOCUMENTS_PROPERTY_VISIBILITY_PRIVATE);
|
||
gdata_documents_property_set_value (parent_id_property, gdata_entry_get_id (parent));
|
||
|
||
gdata_documents_entry_add_documents_property (GDATA_DOCUMENTS_ENTRY (folder), source_id_property);
|
||
gdata_documents_entry_add_documents_property (GDATA_DOCUMENTS_ENTRY (folder), parent_id_property);
|
||
|
||
gdata_entry_set_title (GDATA_ENTRY (folder), gdata_entry_get_title (same_id_entry));
|
||
|
||
g_clear_object (&source_id_property);
|
||
g_clear_object (&parent_id_property);
|
||
}
|
||
else
|
||
gdata_entry_set_title (GDATA_ENTRY (folder), basename);
|
||
|
||
gdata_entry_set_summary (GDATA_ENTRY (folder), summary);
|
||
|
||
error = NULL;
|
||
new_folder = gdata_documents_service_add_entry_to_folder (self->service,
|
||
GDATA_DOCUMENTS_ENTRY (folder),
|
||
GDATA_DOCUMENTS_FOLDER (parent),
|
||
cancellable,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (GDATA_ENTRY (new_folder)), NULL);
|
||
g_debug (" new entry path: %s\n", entry_path);
|
||
|
||
insert_entry (self, GDATA_ENTRY (new_folder));
|
||
g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_clear_object (&folder);
|
||
g_clear_object (&new_folder);
|
||
g_free (basename);
|
||
g_free (entry_path);
|
||
g_free (parent_path);
|
||
g_debug ("- make_directory\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static GList *
|
||
query_shared_drives (GVfsBackendGoogle *self,
|
||
GCancellable *cancellable,
|
||
GError **error)
|
||
{
|
||
GDataDocumentsDriveQuery *query;
|
||
GList *shared_drives = NULL;
|
||
|
||
query = gdata_documents_drive_query_new (NULL);
|
||
while (TRUE)
|
||
{
|
||
GDataDocumentsFeed *feed;
|
||
GList *entries;
|
||
|
||
feed = gdata_documents_service_query_drives (self->service,
|
||
query,
|
||
cancellable,
|
||
NULL,
|
||
NULL,
|
||
error);
|
||
if (feed == NULL)
|
||
break;
|
||
|
||
entries = gdata_feed_get_entries (GDATA_FEED (feed));
|
||
if (entries == NULL)
|
||
{
|
||
g_object_unref (feed);
|
||
break;
|
||
}
|
||
|
||
shared_drives = g_list_concat (shared_drives,
|
||
g_list_copy_deep (entries, (GCopyFunc) g_object_ref, NULL));
|
||
|
||
gdata_query_next_page (GDATA_QUERY (query));
|
||
g_object_unref (feed);
|
||
}
|
||
|
||
g_clear_object (&query);
|
||
|
||
return shared_drives;
|
||
}
|
||
|
||
static void
|
||
g_vfs_backend_google_mount (GVfsBackend *_self,
|
||
GVfsJobMount *job,
|
||
GMountSpec *spec,
|
||
GMountSource *source,
|
||
gboolean is_automount)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataAuthorizationDomain *auth_domain;
|
||
GError *error;
|
||
GList *accounts = NULL;
|
||
GList *l;
|
||
gboolean account_found = FALSE;
|
||
const gchar *host;
|
||
const gchar *user;
|
||
|
||
g_debug ("+ mount\n");
|
||
|
||
error = NULL;
|
||
self->client = goa_client_new_sync (cancellable, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
host = g_mount_spec_get (spec, "host");
|
||
user = g_mount_spec_get (spec, "user");
|
||
self->account_identity = g_strconcat (user, "@", host, NULL);
|
||
|
||
accounts = goa_client_get_accounts (self->client);
|
||
for (l = accounts; l != NULL && !account_found; l = l->next)
|
||
{
|
||
GoaAccount *account;
|
||
GoaObject *object = GOA_OBJECT (l->data);
|
||
gchar *account_identity;
|
||
gchar *provider_type;
|
||
|
||
account = goa_object_get_account (object);
|
||
account_identity = goa_account_dup_identity (account);
|
||
provider_type = goa_account_dup_provider_type (account);
|
||
|
||
if (g_strcmp0 (provider_type, "google") == 0 &&
|
||
g_strcmp0 (account_identity, self->account_identity) == 0)
|
||
{
|
||
GDataGoaAuthorizer *authorizer;
|
||
|
||
authorizer = gdata_goa_authorizer_new (object);
|
||
self->service = gdata_documents_service_new (GDATA_AUTHORIZER (authorizer));
|
||
account_found = TRUE;
|
||
g_object_unref (authorizer);
|
||
}
|
||
|
||
g_free (provider_type);
|
||
g_free (account_identity);
|
||
g_object_unref (account);
|
||
}
|
||
|
||
if (!account_found)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, _("Invalid mount spec"));
|
||
goto out;
|
||
}
|
||
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
|
||
self->root = GDATA_ENTRY (gdata_documents_folder_new (ROOT_ID));
|
||
gdata_entry_set_title (self->root, self->account_identity);
|
||
|
||
error = NULL;
|
||
self->home = gdata_service_query_single_entry (GDATA_SERVICE (self->service),
|
||
auth_domain,
|
||
"root",
|
||
NULL,
|
||
GDATA_TYPE_DOCUMENTS_FOLDER,
|
||
cancellable,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
insert_custom_entry (self, self->home, ROOT_ID);
|
||
|
||
self->shared_with_me_dir = GDATA_ENTRY (gdata_documents_folder_new (SHARED_WITH_ME_ID));
|
||
/* Translators: This is the "Shared with me" folder on https://drive.google.com. */
|
||
gdata_entry_set_title (self->shared_with_me_dir, _("Shared with me"));
|
||
insert_custom_entry (self, self->shared_with_me_dir, ROOT_ID);
|
||
|
||
self->shared_drives = query_shared_drives (self, cancellable, &error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
if (self->shared_drives)
|
||
{
|
||
self->shared_drives_dir = GDATA_ENTRY (gdata_documents_folder_new (SHARED_DRIVES_ID));
|
||
/* Translators: This is the "Shared drives" folder on https://drive.google.com. */
|
||
gdata_entry_set_title (self->shared_drives_dir, _("Shared drives"));
|
||
insert_custom_entry (self, self->shared_drives_dir, ROOT_ID);
|
||
}
|
||
|
||
/* TODO: Make it work with GOA volume monitor resp. shadow mounts. */
|
||
g_vfs_backend_set_default_location (_self, gdata_entry_get_id (self->home));
|
||
|
||
g_vfs_backend_set_mount_spec (_self, spec);
|
||
g_vfs_backend_set_display_name (_self, self->account_identity);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_list_free_full (accounts, g_object_unref);
|
||
g_debug ("- mount\n");
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_open_icon_for_read (GVfsBackend *_self,
|
||
GVfsJobOpenIconForRead *job,
|
||
const gchar *icon_id)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataAuthorizationDomain *auth_domain;
|
||
GInputStream *stream;
|
||
|
||
g_debug ("+ open_icon_for_read: %s\n", icon_id);
|
||
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
stream = gdata_download_stream_new (GDATA_SERVICE (self->service), auth_domain, icon_id, cancellable);
|
||
if (stream == NULL)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error getting data from file"));
|
||
goto out;
|
||
}
|
||
|
||
g_vfs_job_open_for_read_set_handle (G_VFS_JOB_OPEN_FOR_READ (job), stream);
|
||
g_vfs_job_open_for_read_set_can_seek (G_VFS_JOB_OPEN_FOR_READ (job), TRUE);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_debug ("- open_icon_for_read\n");
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_push (GVfsBackend *_self,
|
||
GVfsJobPush *job,
|
||
const gchar *destination,
|
||
const gchar *local_path,
|
||
GFileCopyFlags flags,
|
||
gboolean remove_source,
|
||
GFileProgressCallback progress_callback,
|
||
gpointer progress_callback_data)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataDocumentsDocument *document = NULL;
|
||
GDataDocumentsDocument *new_document = NULL;
|
||
GDataEntry *destination_parent;
|
||
GDataEntry *existing_entry;
|
||
GDataUploadStream *ostream = NULL;
|
||
GError *error;
|
||
GFile *local_file = NULL;
|
||
GFileInputStream *istream = NULL;
|
||
GFileInfo *info = NULL;
|
||
gboolean needs_overwrite = FALSE;
|
||
const gchar *content_type;
|
||
const gchar *title;
|
||
gchar *destination_basename = NULL;
|
||
gchar *entry_path = NULL;
|
||
gchar *parent_path = NULL;
|
||
gchar *local_file_title = NULL;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ push: %s -> %s, %d\n", local_path, destination, flags);
|
||
|
||
if (remove_source && (flags & G_FILE_COPY_NO_FALLBACK_FOR_MOVE))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
if (flags & G_FILE_COPY_BACKUP)
|
||
{
|
||
/* Return G_IO_ERROR_NOT_SUPPORTED instead of
|
||
* G_IO_ERROR_CANT_CREATE_BACKUP to proceed with the GIO
|
||
* fallback copy.
|
||
*/
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
local_file = g_file_new_for_path (local_path);
|
||
|
||
error = NULL;
|
||
info = g_file_query_info (local_file,
|
||
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE","
|
||
G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME","
|
||
G_FILE_ATTRIBUTE_STANDARD_TYPE","
|
||
G_FILE_ATTRIBUTE_STANDARD_SIZE,
|
||
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;
|
||
}
|
||
|
||
local_file_title = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
|
||
|
||
error = NULL;
|
||
destination_parent = resolve_dir (self, destination, cancellable, &destination_basename, &parent_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
if (destination_parent == self->root || destination_parent == self->shared_with_me_dir || destination_parent == self->shared_drives_dir)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
existing_entry = resolve_child (self, destination_parent, destination_basename, cancellable, NULL);
|
||
if (existing_entry != NULL)
|
||
{
|
||
const gchar *existing_entry_id;
|
||
gint64 num_same_title_files;
|
||
|
||
existing_entry_id = gdata_entry_get_id (existing_entry);
|
||
num_same_title_files = count_files_in_directory_with_title (self,
|
||
destination_basename,
|
||
destination_parent);
|
||
|
||
g_debug (" count of files with title same as destination_basename: %ld\n", num_same_title_files);
|
||
|
||
if (flags & G_FILE_COPY_OVERWRITE)
|
||
{
|
||
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (existing_entry))
|
||
{
|
||
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_MERGE,
|
||
_("Can’t copy directory over directory"));
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_IS_DIRECTORY,
|
||
_("Can’t copy file over directory"));
|
||
goto out;
|
||
}
|
||
}
|
||
else if (is_native_file (existing_entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_NOT_REGULAR_FILE,
|
||
_("Target file is not a regular file"));
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_RECURSE,
|
||
_("Can’t recursively copy directory"));
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
if (g_strcmp0 (local_file_title, existing_entry_id) == 0)
|
||
{
|
||
/* This corresponds to the operation when a local file has its title set to the
|
||
* real ID of some file, and that the local file is being pushed with the same title.
|
||
* We disallow such operation. */
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_FAILED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
if (num_same_title_files > 1)
|
||
{
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_FAILED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
if (num_same_title_files == 1 || g_strcmp0 (destination_basename, existing_entry_id) == 0)
|
||
needs_overwrite = TRUE;
|
||
}
|
||
else
|
||
{
|
||
if (num_same_title_files > 0 ||
|
||
g_strcmp0 (destination_basename, existing_entry_id) == 0 ||
|
||
g_strcmp0 (local_file_title, existing_entry_id) == 0)
|
||
{
|
||
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
|
||
goto out;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_WOULD_RECURSE,
|
||
_("Can’t recursively copy directory"));
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
g_debug (" will overwrite: %d\n", needs_overwrite);
|
||
|
||
error = NULL;
|
||
istream = g_file_read (local_file, cancellable, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
content_type = g_file_info_get_content_type (info);
|
||
|
||
if (needs_overwrite)
|
||
{
|
||
document = GDATA_DOCUMENTS_DOCUMENT (g_object_ref (existing_entry));
|
||
title = local_file_title;
|
||
|
||
error = NULL;
|
||
ostream = gdata_documents_service_update_document (self->service,
|
||
document,
|
||
title,
|
||
content_type,
|
||
cancellable,
|
||
&error);
|
||
}
|
||
else
|
||
{
|
||
document = gdata_documents_document_new (NULL);
|
||
title = destination_basename;
|
||
gdata_entry_set_title (GDATA_ENTRY (document), title);
|
||
|
||
error = NULL;
|
||
ostream = gdata_documents_service_upload_document (self->service,
|
||
document,
|
||
title,
|
||
content_type,
|
||
GDATA_DOCUMENTS_FOLDER (destination_parent),
|
||
cancellable,
|
||
&error);
|
||
}
|
||
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
error = NULL;
|
||
gvfs_output_stream_splice (G_OUTPUT_STREAM (ostream),
|
||
G_INPUT_STREAM (istream),
|
||
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
|
||
g_file_info_get_size (info),
|
||
progress_callback,
|
||
progress_callback_data,
|
||
cancellable,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
error = NULL;
|
||
new_document = gdata_documents_service_finish_upload (self->service, ostream, &error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (GDATA_ENTRY (new_document)), NULL);
|
||
g_debug (" new entry path: %s\n", entry_path);
|
||
|
||
if (needs_overwrite)
|
||
remove_entry (self, existing_entry);
|
||
|
||
insert_entry (self, GDATA_ENTRY (new_document));
|
||
g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
|
||
|
||
if (remove_source)
|
||
{
|
||
error = NULL;
|
||
g_file_delete (local_file, cancellable, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_clear_object (&document);
|
||
g_clear_object (&info);
|
||
g_clear_object (&istream);
|
||
g_clear_object (&local_file);
|
||
g_clear_object (&new_document);
|
||
g_clear_object (&ostream);
|
||
g_free (destination_basename);
|
||
g_free (local_file_title);
|
||
g_free (entry_path);
|
||
g_free (parent_path);
|
||
g_debug ("- push\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
fs_info_cb (GObject *source_object,
|
||
GAsyncResult *res,
|
||
gpointer user_data)
|
||
{
|
||
GDataDocumentsService *service = GDATA_DOCUMENTS_SERVICE (source_object);
|
||
GVfsJobQueryFsInfo *job = G_VFS_JOB_QUERY_FS_INFO (user_data);
|
||
GError *error = NULL;
|
||
GDataDocumentsMetadata *metadata;
|
||
goffset total, used;
|
||
|
||
metadata = gdata_documents_service_get_metadata_finish (service, res, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
total = gdata_documents_metadata_get_quota_total (metadata);
|
||
used = gdata_documents_metadata_get_quota_used (metadata);
|
||
g_object_unref (metadata);
|
||
|
||
if (used >= 0) /* sanity check */
|
||
g_file_info_set_attribute_uint64 (job->file_info, G_FILE_ATTRIBUTE_FILESYSTEM_USED, used);
|
||
|
||
if (total >= 0) /* -1 'total' means unlimited quota, just don't report size in that case */
|
||
g_file_info_set_attribute_uint64 (job->file_info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE, total);
|
||
|
||
if (total >= 0 && used >= 0)
|
||
g_file_info_set_attribute_uint64 (job->file_info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE, total - used);
|
||
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_debug ("- query_fs_info\n");
|
||
}
|
||
|
||
static gboolean
|
||
g_vfs_backend_google_query_fs_info (GVfsBackend *_self,
|
||
GVfsJobQueryFsInfo *job,
|
||
const gchar *filename,
|
||
GFileInfo *info,
|
||
GFileAttributeMatcher *matcher)
|
||
{
|
||
GMountSpec *spec;
|
||
const gchar *type;
|
||
|
||
g_debug ("+ query_fs_info: %s\n", filename);
|
||
|
||
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_READONLY, FALSE);
|
||
|
||
spec = g_vfs_backend_get_mount_spec (_self);
|
||
type = g_mount_spec_get_type (spec);
|
||
g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_FILESYSTEM_TYPE, type);
|
||
g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_FILESYSTEM_REMOTE, TRUE);
|
||
g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_FILESYSTEM_USE_PREVIEW, G_FILESYSTEM_PREVIEW_TYPE_IF_ALWAYS);
|
||
|
||
if (g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) ||
|
||
g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_FREE) ||
|
||
g_file_attribute_matcher_matches (matcher, G_FILE_ATTRIBUTE_FILESYSTEM_USED))
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
gdata_documents_service_get_metadata_async (self->service, cancellable, fs_info_cb, job);
|
||
return TRUE;
|
||
}
|
||
|
||
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;
|
||
gchar *entry_path = NULL;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ query_info: %s, %d\n", filename, flags);
|
||
log_dir_entries (self);
|
||
|
||
error = NULL;
|
||
entry = resolve (self, filename, cancellable, &entry_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_debug (" entry path: %s\n", entry_path);
|
||
|
||
error = NULL;
|
||
build_file_info (self, entry, flags, info, matcher, filename, entry_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_free (entry_path);
|
||
g_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);
|
||
const gchar *filename;
|
||
gchar *entry_path = NULL;
|
||
|
||
g_debug ("+ query_info_on_read: %p\n", handle);
|
||
|
||
entry = g_object_get_data (G_OBJECT (stream), "g-vfs-backend-google-entry");
|
||
filename = g_object_get_data (G_OBJECT (stream), "g-vfs-backend-google-filename");
|
||
entry_path = g_object_get_data (G_OBJECT (stream), "g-vfs-backend-google-entry-path");
|
||
|
||
g_debug (" entry path: %s\n", entry_path);
|
||
|
||
error = NULL;
|
||
build_file_info (self,
|
||
entry,
|
||
G_FILE_QUERY_INFO_NONE,
|
||
info,
|
||
matcher,
|
||
filename,
|
||
entry_path,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_debug ("- query_info_on_read\n");
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static gboolean
|
||
g_vfs_backend_google_query_info_on_write (GVfsBackend *_self,
|
||
GVfsJobQueryInfoWrite *job,
|
||
GVfsBackendHandle handle,
|
||
GFileInfo *info,
|
||
GFileAttributeMatcher *matcher)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GError *error;
|
||
WriteHandle *wh = (WriteHandle *) handle;
|
||
|
||
g_debug ("+ query_info_on_write: %p\n", handle);
|
||
g_debug (" entry path: %s\n", wh->entry_path);
|
||
|
||
error = NULL;
|
||
build_file_info (self,
|
||
wh->document,
|
||
G_FILE_QUERY_INFO_NONE,
|
||
info,
|
||
matcher,
|
||
wh->filename,
|
||
wh->entry_path,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_debug ("- query_info_on_write\n");
|
||
return TRUE;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_open_for_read (GVfsBackend *_self,
|
||
GVfsJobOpenForRead *job,
|
||
const gchar *filename)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataEntry *entry;
|
||
GInputStream *stream;
|
||
GError *error;
|
||
gchar *content_type = NULL;
|
||
gchar *entry_path = NULL;
|
||
GDataAuthorizationDomain *auth_domain;
|
||
const gchar *uri;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ open_for_read: %s\n", filename);
|
||
|
||
error = NULL;
|
||
entry = resolve (self, filename, cancellable, &entry_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_debug (" entry path: %s\n", entry_path);
|
||
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("Can’t open directory"));
|
||
goto out;
|
||
}
|
||
|
||
content_type = get_content_type_from_entry (entry);
|
||
if (content_type == NULL)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid reply received"));
|
||
goto out;
|
||
}
|
||
|
||
if (is_native_file (entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_REGULAR_FILE, _("File is not a regular file"));
|
||
goto out;
|
||
}
|
||
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
uri = gdata_entry_get_content_uri (entry);
|
||
stream = gdata_download_stream_new (GDATA_SERVICE (self->service), auth_domain, uri, cancellable);
|
||
if (stream == NULL)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error getting data from file"));
|
||
goto out;
|
||
}
|
||
|
||
g_object_set_data_full (G_OBJECT (stream), "g-vfs-backend-google-entry", g_object_ref (entry), g_object_unref);
|
||
g_object_set_data_full (G_OBJECT (stream), "g-vfs-backend-google-filename", g_strdup (filename), g_free);
|
||
g_object_set_data_full (G_OBJECT (stream), "g-vfs-backend-google-entry-path", g_strdup (entry_path), g_free);
|
||
g_vfs_job_open_for_read_set_handle (job, stream);
|
||
g_vfs_job_open_for_read_set_can_seek (job, TRUE);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_free (content_type);
|
||
g_free (entry_path);
|
||
g_debug ("- open_for_read\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
read_cb (GObject *source_object,
|
||
GAsyncResult *res,
|
||
gpointer user_data)
|
||
{
|
||
GError *error = NULL;
|
||
GInputStream *stream = G_INPUT_STREAM (source_object);
|
||
GVfsJobRead *job = G_VFS_JOB_READ (user_data);
|
||
gssize nread;
|
||
|
||
nread = g_input_stream_read_finish (stream, res, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_vfs_job_read_set_size (job, (gsize) nread);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_debug ("- read\n");
|
||
}
|
||
|
||
static gboolean
|
||
g_vfs_backend_google_read (GVfsBackend *_self,
|
||
GVfsJobRead *job,
|
||
GVfsBackendHandle handle,
|
||
gchar *buffer,
|
||
gsize bytes_requested)
|
||
{
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GInputStream *stream = G_INPUT_STREAM (handle);
|
||
|
||
g_debug ("+ read: %p\n", handle);
|
||
g_input_stream_read_async (stream, buffer, bytes_requested, G_PRIORITY_DEFAULT, cancellable, read_cb, job);
|
||
return TRUE;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_seek_on_read (GVfsBackend *_self,
|
||
GVfsJobSeekRead *job,
|
||
GVfsBackendHandle handle,
|
||
goffset offset,
|
||
GSeekType type)
|
||
{
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GError *error;
|
||
GInputStream *stream = G_INPUT_STREAM (handle);
|
||
goffset cur_offset;
|
||
|
||
g_debug ("+ seek_on_read: %p\n", handle);
|
||
|
||
error = NULL;
|
||
g_seekable_seek (G_SEEKABLE (stream), offset, type, cancellable, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
cur_offset = g_seekable_tell (G_SEEKABLE (stream));
|
||
g_vfs_job_seek_read_set_offset (job, cur_offset);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_debug ("- seek_on_read\n");
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
close_read_cb (GObject *source_object,
|
||
GAsyncResult *res,
|
||
gpointer user_data)
|
||
{
|
||
GError *error = NULL;
|
||
GInputStream *stream = G_INPUT_STREAM (source_object);
|
||
GVfsJobCloseRead *job = G_VFS_JOB_CLOSE_READ (user_data);
|
||
|
||
g_input_stream_close_finish (stream, res, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_object_unref (stream);
|
||
g_debug ("- close_read\n");
|
||
}
|
||
|
||
static gboolean
|
||
g_vfs_backend_google_close_read (GVfsBackend *_self,
|
||
GVfsJobCloseRead *job,
|
||
GVfsBackendHandle handle)
|
||
{
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GInputStream *stream = G_INPUT_STREAM (handle);
|
||
|
||
g_debug ("+ close_read: %p\n", handle);
|
||
|
||
g_input_stream_close_async (stream, G_PRIORITY_DEFAULT, cancellable, close_read_cb, job);
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_set_display_name (GVfsBackend *_self,
|
||
GVfsJobSetDisplayName *job,
|
||
const gchar *filename,
|
||
const gchar *display_name)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataAuthorizationDomain *auth_domain;
|
||
GDataEntry *entry;
|
||
GDataEntry *new_entry = NULL;
|
||
GDataEntry *parent;
|
||
GError *error;
|
||
gchar *entry_path = NULL;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ set_display_name: %s, %s\n", filename, display_name);
|
||
|
||
error = NULL;
|
||
entry = resolve (self, filename, cancellable, &entry_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_debug (" entry path: %s\n", entry_path);
|
||
|
||
parent = resolve_dir (self, filename, cancellable, NULL, NULL, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
if (entry == self->root || entry == self->home || entry == self->shared_with_me_dir || parent == self->shared_drives_dir)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
/* The internal ref count has to be increased before removing the entry since
|
||
* remove_entry_full calls g_object_unref(). */
|
||
g_object_ref (entry);
|
||
remove_entry (self, entry);
|
||
|
||
gdata_entry_set_title (entry, display_name);
|
||
auth_domain = gdata_documents_service_get_primary_authorization_domain ();
|
||
|
||
error = NULL;
|
||
new_entry = gdata_service_update_entry (GDATA_SERVICE (self->service), auth_domain, entry, cancellable, &error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
g_object_unref (entry);
|
||
goto out;
|
||
}
|
||
|
||
insert_entry (self, new_entry);
|
||
g_hash_table_foreach (self->monitors, emit_attribute_changed_event, entry_path);
|
||
g_vfs_job_set_display_name_set_new_path (job, entry_path);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
g_object_unref (entry);
|
||
|
||
out:
|
||
g_clear_object (&new_entry);
|
||
g_free (entry_path);
|
||
g_debug ("- set_display_name\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_create (GVfsBackend *_self,
|
||
GVfsJobOpenForWrite *job,
|
||
const gchar *filename,
|
||
GFileCreateFlags flags)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataDocumentsDocument *document = NULL;
|
||
GDataDocumentsEntry *new_document = NULL;
|
||
GDataEntry *existing_entry;
|
||
GDataEntry *parent;
|
||
GError *error;
|
||
WriteHandle *handle;
|
||
gchar *basename = NULL;
|
||
gchar *entry_path = NULL;
|
||
gchar *parent_path = NULL;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ create: %s, %d\n", filename, flags);
|
||
|
||
if (g_strcmp0 (filename, "/") == 0)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
error = NULL;
|
||
parent = resolve_dir (self, filename, cancellable, &basename, &parent_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_debug (" parent path: %s\n", parent_path);
|
||
|
||
if (parent == self->root || parent == self->shared_with_me_dir || parent == self->shared_drives_dir)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
existing_entry = resolve_child (self, parent, basename, cancellable, NULL);
|
||
if (existing_entry != NULL)
|
||
{
|
||
if (flags & G_FILE_CREATE_REPLACE_DESTINATION)
|
||
{
|
||
g_vfs_job_failed_literal (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_NOT_SUPPORTED,
|
||
_("Operation not supported"));
|
||
goto out;
|
||
}
|
||
else
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
document = gdata_documents_document_new (NULL);
|
||
gdata_entry_set_title (GDATA_ENTRY (document), basename);
|
||
|
||
error = NULL;
|
||
new_document = gdata_documents_service_add_entry_to_folder (self->service,
|
||
GDATA_DOCUMENTS_ENTRY (document),
|
||
GDATA_DOCUMENTS_FOLDER (parent),
|
||
cancellable,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (GDATA_ENTRY (new_document)), NULL);
|
||
g_debug (" new entry path: %s\n", entry_path);
|
||
|
||
insert_entry (self, GDATA_ENTRY (new_document));
|
||
g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
|
||
|
||
handle = write_handle_new (GDATA_ENTRY (new_document), NULL, filename, entry_path);
|
||
g_vfs_job_open_for_write_set_handle (job, handle);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_clear_object (&document);
|
||
g_clear_object (&new_document);
|
||
g_free (basename);
|
||
g_free (entry_path);
|
||
g_free (parent_path);
|
||
g_debug ("- create\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_replace (GVfsBackend *_self,
|
||
GVfsJobOpenForWrite *job,
|
||
const gchar *filename,
|
||
const gchar *etag,
|
||
gboolean make_backup,
|
||
GFileCreateFlags flags)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataDocumentsDocument *document = NULL;
|
||
GDataDocumentsEntry *new_document = NULL;
|
||
GDataEntry *existing_entry;
|
||
GDataEntry *parent;
|
||
GDataUploadStream *stream = NULL;
|
||
GError *error;
|
||
WriteHandle *handle;
|
||
gboolean needs_overwrite = FALSE;
|
||
gchar *basename = NULL;
|
||
gchar *content_type = NULL;
|
||
gchar *entry_path = NULL;
|
||
gchar *parent_path = NULL;
|
||
|
||
g_rec_mutex_lock (&self->mutex);
|
||
g_debug ("+ replace: %s, %s, %d, %d\n", filename, etag, make_backup, flags);
|
||
|
||
if (make_backup)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_CANT_CREATE_BACKUP,
|
||
_("Backup file creation failed"));
|
||
goto out;
|
||
}
|
||
|
||
if (g_strcmp0 (filename, "/") == 0)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
error = NULL;
|
||
parent = resolve_dir (self, filename, cancellable, &basename, &parent_path, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_debug (" parent path: %s\n", parent_path);
|
||
|
||
if (parent == self->root || parent == self->shared_with_me_dir || parent == self->shared_drives_dir)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, _("Operation not supported"));
|
||
goto out;
|
||
}
|
||
|
||
existing_entry = resolve_child (self, parent, basename, cancellable, NULL);
|
||
if (existing_entry != NULL)
|
||
{
|
||
if (GDATA_IS_DOCUMENTS_FOLDER (existing_entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY, _("Target file is a directory"));
|
||
goto out;
|
||
}
|
||
else if (is_native_file (existing_entry))
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job),
|
||
G_IO_ERROR,
|
||
G_IO_ERROR_NOT_REGULAR_FILE,
|
||
_("Target file is not a regular file"));
|
||
goto out;
|
||
}
|
||
|
||
needs_overwrite = TRUE;
|
||
}
|
||
|
||
g_debug (" will overwrite: %d\n", needs_overwrite);
|
||
|
||
if (needs_overwrite)
|
||
{
|
||
const gchar *title;
|
||
|
||
entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (existing_entry), NULL);
|
||
g_debug (" existing entry path: %s\n", entry_path);
|
||
|
||
title = gdata_entry_get_title (existing_entry);
|
||
content_type = get_content_type_from_entry (existing_entry);
|
||
|
||
error = NULL;
|
||
stream = gdata_documents_service_update_document (self->service,
|
||
GDATA_DOCUMENTS_DOCUMENT (existing_entry),
|
||
title,
|
||
content_type,
|
||
cancellable,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
handle = write_handle_new (NULL, stream, filename, entry_path);
|
||
}
|
||
else
|
||
{
|
||
document = gdata_documents_document_new (NULL);
|
||
gdata_entry_set_title (GDATA_ENTRY (document), basename);
|
||
|
||
error = NULL;
|
||
new_document = gdata_documents_service_add_entry_to_folder (self->service,
|
||
GDATA_DOCUMENTS_ENTRY (document),
|
||
GDATA_DOCUMENTS_FOLDER (parent),
|
||
cancellable,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
entry_path = g_build_path ("/", parent_path, gdata_entry_get_id (GDATA_ENTRY (new_document)), NULL);
|
||
g_debug (" new entry path: %s\n", entry_path);
|
||
|
||
insert_entry (self, GDATA_ENTRY (new_document));
|
||
g_hash_table_foreach (self->monitors, emit_create_event, entry_path);
|
||
|
||
handle = write_handle_new (GDATA_ENTRY (new_document), NULL, filename, entry_path);
|
||
}
|
||
|
||
g_vfs_job_open_for_write_set_handle (job, handle);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_clear_object (&document);
|
||
g_clear_object (&new_document);
|
||
g_clear_object (&stream);
|
||
g_free (basename);
|
||
g_free (content_type);
|
||
g_free (entry_path);
|
||
g_free (parent_path);
|
||
g_debug ("- replace\n");
|
||
g_rec_mutex_unlock (&self->mutex);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_write (GVfsBackend *_self,
|
||
GVfsJobWrite *job,
|
||
GVfsBackendHandle handle,
|
||
gchar *buffer,
|
||
gsize buffer_size)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GError *error;
|
||
WriteHandle *wh = (WriteHandle *) handle;
|
||
gssize nwrite;
|
||
|
||
g_debug ("+ write: %p\n", handle);
|
||
|
||
if (wh->stream == NULL)
|
||
{
|
||
const gchar *title;
|
||
gchar *content_type = NULL;
|
||
|
||
title = gdata_entry_get_title (wh->document);
|
||
content_type = g_content_type_guess (title, (const guchar *) buffer, buffer_size, NULL);
|
||
g_debug (" content-type: %s\n", content_type);
|
||
|
||
error = NULL;
|
||
wh->stream = gdata_documents_service_update_document (self->service,
|
||
GDATA_DOCUMENTS_DOCUMENT (wh->document),
|
||
title,
|
||
content_type,
|
||
cancellable,
|
||
&error);
|
||
g_free (content_type);
|
||
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
g_debug (" writing to stream: %p\n", wh->stream);
|
||
g_debug (" entry path: %s\n", wh->entry_path);
|
||
|
||
error = NULL;
|
||
nwrite = g_output_stream_write (G_OUTPUT_STREAM (wh->stream),
|
||
buffer,
|
||
buffer_size,
|
||
cancellable,
|
||
&error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
|
||
g_hash_table_foreach (self->monitors, emit_changed_event, wh->entry_path);
|
||
g_vfs_job_write_set_written_size (job, (gsize) nwrite);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_debug ("- write\n");
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_close_write (GVfsBackend *_self,
|
||
GVfsJobCloseWrite *job,
|
||
GVfsBackendHandle handle)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
GCancellable *cancellable = G_VFS_JOB (job)->cancellable;
|
||
GDataDocumentsDocument *new_document = NULL;
|
||
GError *error;
|
||
WriteHandle *wh = (WriteHandle *) handle;
|
||
|
||
g_debug ("+ close_write: %p\n", handle);
|
||
|
||
if (!g_output_stream_is_closed (G_OUTPUT_STREAM (wh->stream)))
|
||
{
|
||
error = NULL;
|
||
g_output_stream_close (G_OUTPUT_STREAM (wh->stream), cancellable, &error);
|
||
if (error != NULL)
|
||
{
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
}
|
||
|
||
error = NULL;
|
||
new_document = gdata_documents_service_finish_upload (self->service, wh->stream, &error);
|
||
if (error != NULL)
|
||
{
|
||
sanitize_error (&error);
|
||
g_vfs_job_failed_from_error (G_VFS_JOB (job), error);
|
||
g_error_free (error);
|
||
goto out;
|
||
}
|
||
else if (new_document == NULL)
|
||
{
|
||
g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_FAILED, _("Error writing file"));
|
||
goto out;
|
||
}
|
||
|
||
g_debug (" new entry path: %s\n", wh->entry_path);
|
||
|
||
remove_entry (self, wh->document);
|
||
insert_entry (self, GDATA_ENTRY (new_document));
|
||
g_hash_table_foreach (self->monitors, emit_changes_done_event, wh->entry_path);
|
||
g_vfs_job_succeeded (G_VFS_JOB (job));
|
||
|
||
out:
|
||
g_clear_object (&new_document);
|
||
write_handle_free (wh);
|
||
g_debug ("- close_write\n");
|
||
}
|
||
|
||
/* ---------------------------------------------------------------------------------------------------- */
|
||
|
||
static void
|
||
g_vfs_backend_google_dispose (GObject *_self)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
|
||
if (self->dir_collisions != NULL)
|
||
{
|
||
g_list_free_full (self->dir_collisions, g_object_unref);
|
||
self->dir_collisions = NULL;
|
||
}
|
||
|
||
g_clear_object (&self->service);
|
||
g_clear_object (&self->root);
|
||
g_clear_object (&self->home);
|
||
g_clear_object (&self->shared_with_me_dir);
|
||
g_clear_object (&self->shared_drives_dir);
|
||
g_clear_list (&self->shared_drives, g_object_unref);
|
||
g_clear_object (&self->client);
|
||
g_clear_pointer (&self->entries, g_hash_table_unref);
|
||
g_clear_pointer (&self->dir_entries, g_hash_table_unref);
|
||
g_clear_pointer (&self->dir_timestamps, g_hash_table_unref);
|
||
|
||
G_OBJECT_CLASS (g_vfs_backend_google_parent_class)->dispose (_self);
|
||
}
|
||
|
||
static void
|
||
g_vfs_backend_google_finalize (GObject *_self)
|
||
{
|
||
GVfsBackendGoogle *self = G_VFS_BACKEND_GOOGLE (_self);
|
||
|
||
g_hash_table_foreach (self->monitors, remove_monitor_weak_ref, self->monitors);
|
||
g_hash_table_unref (self->monitors);
|
||
g_free (self->account_identity);
|
||
|
||
g_rec_mutex_clear (&self->mutex);
|
||
|
||
G_OBJECT_CLASS (g_vfs_backend_google_parent_class)->finalize (_self);
|
||
}
|
||
|
||
static void
|
||
g_vfs_backend_google_class_init (GVfsBackendGoogleClass * klass)
|
||
{
|
||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||
GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass);
|
||
|
||
gobject_class->dispose = g_vfs_backend_google_dispose;
|
||
gobject_class->finalize = g_vfs_backend_google_finalize;
|
||
|
||
backend_class->try_close_read = g_vfs_backend_google_close_read;
|
||
backend_class->close_write = g_vfs_backend_google_close_write;
|
||
backend_class->copy = g_vfs_backend_google_copy;
|
||
backend_class->move = g_vfs_backend_google_move;
|
||
backend_class->create = g_vfs_backend_google_create;
|
||
backend_class->try_create_dir_monitor = g_vfs_backend_google_create_dir_monitor;
|
||
backend_class->delete = g_vfs_backend_google_delete;
|
||
backend_class->enumerate = g_vfs_backend_google_enumerate;
|
||
backend_class->make_directory = g_vfs_backend_google_make_directory;
|
||
backend_class->mount = g_vfs_backend_google_mount;
|
||
backend_class->open_for_read = g_vfs_backend_google_open_for_read;
|
||
backend_class->open_icon_for_read = g_vfs_backend_google_open_icon_for_read;
|
||
backend_class->push = g_vfs_backend_google_push;
|
||
backend_class->try_query_fs_info = g_vfs_backend_google_query_fs_info;
|
||
backend_class->query_info = g_vfs_backend_google_query_info;
|
||
backend_class->query_info_on_read = g_vfs_backend_google_query_info_on_read;
|
||
backend_class->try_query_info_on_write = g_vfs_backend_google_query_info_on_write;
|
||
backend_class->seek_on_read = g_vfs_backend_google_seek_on_read;
|
||
backend_class->set_display_name = g_vfs_backend_google_set_display_name;
|
||
backend_class->try_read = g_vfs_backend_google_read;
|
||
backend_class->replace = g_vfs_backend_google_replace;
|
||
backend_class->write = g_vfs_backend_google_write;
|
||
}
|
||
|
||
static void
|
||
g_vfs_backend_google_init (GVfsBackendGoogle *self)
|
||
{
|
||
self->entries = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
|
||
self->dir_entries = g_hash_table_new_full (entries_in_folder_hash,
|
||
entries_in_folder_equal,
|
||
dir_entries_key_free,
|
||
g_object_unref);
|
||
self->dir_timestamps = 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);
|
||
}
|