[gvfs] google: Changed copy, move and push to enable overwriting files



commit 6dceb6a4567bab9fe4bd8370271e16043f70488c
Author: Mayank Sharma <mayank8019 gmail com>
Date:   Wed Jan 8 15:24:46 2020 +0530

    google: Changed copy, move and push to enable overwriting files
    
    Until this commit, copy and move operations weren't capable of
    performing an actual overwrite of file contents and used to report an
    error (G_IO_ERROR_NOT_SUPPORTED). This commit adds the functionality to
    perform an overwrite operation if there exists another file with the
    same title in the destination directory. This behaviour is desired
    because it conforms to how normal copy/move operations happen in normal
    POSIX world.
    
    Since, multiple files with same title can exist in destination folder,
    the copy/move operation now explicity checks and only allows the
    overwrite operation in case there exists a single file with same title
    as the source file, otherwise it performs a blind copy/move. (Blind copy
    here means that a completely new file gets created with same title as
    source in case of copy, whereas for move the source file simply gets moved).
    
    The push function has been modified as well to behave similar to
    copy/move.

 daemon/gvfsbackendgoogle.c | 597 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 487 insertions(+), 110 deletions(-)
---
diff --git a/daemon/gvfsbackendgoogle.c b/daemon/gvfsbackendgoogle.c
index e5989895..d4b074e2 100644
--- a/daemon/gvfsbackendgoogle.c
+++ b/daemon/gvfsbackendgoogle.c
@@ -393,6 +393,30 @@ get_parent_ids (GVfsBackendGoogle *self,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+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)
 {
@@ -1392,6 +1416,7 @@ g_vfs_backend_google_copy (GVfsBackend           *_self,
   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;
@@ -1436,32 +1461,40 @@ g_vfs_backend_google_copy (GVfsBackend           *_self,
       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)
     {
-      /* We don't support overwrites, so we don't need to care
-       * about G_IO_ERROR_IS_DIRECTORY and G_IO_ERROR_WOULD_MERGE. */
+      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.
-           *
-           * Doing the below check allows us to support the following sequence
-           * of operations -
-           * 1. `gio copy id1 id2/foo`
-           * 2. `gio copy id1 id2/foo`
-           *
-           * Moreover, it disallows operations where foo is the real ID of some
-           * other file existing in folder with ID `id2`. Although, in-practive
-           * we can allow this operation, but later when query_info will be called,
-           * it will resolve to some other entry instead of the expected entry. */
-          if (g_strcmp0 (gdata_entry_get_id (existing_entry), destination_basename) == 0)
+           * 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
@@ -1479,29 +1512,30 @@ g_vfs_backend_google_copy (GVfsBackend           *_self,
                                         _("Operation not supported"));
               goto out;
             }
-          else if (g_strcmp0 (gdata_entry_get_title (existing_entry), destination_basename) == 0)
+          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
             {
-              /* Case occurs when doing `gio copy ./id1 ./$TITLE$` where $TITLE$ is
-               * the title of file with ID `id1` */
-              g_vfs_job_failed_literal (G_VFS_JOB (job),
-                                        G_IO_ERROR,
-                                        G_IO_ERROR_FAILED,
-                                        _("Operation not supported"));
-              goto out;
+              /* 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
         {
-          /* If the destination_basename matches the volatile path of some
-           * other file (given by existing_entry) and if the titles are
-           * different, copy 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.
+          /* 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).
            *
-           * This isn't an issue since we've already handled those extra
-           * entries in insert_entry_full().
-           */
-          if (g_strcmp0 (gdata_entry_get_title (existing_entry), title) == 0)
+           * 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;
@@ -1509,6 +1543,86 @@ g_vfs_backend_google_copy (GVfsBackend           *_self,
         }
     }
 
+  /* 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
@@ -1553,11 +1667,19 @@ g_vfs_backend_google_copy (GVfsBackend           *_self,
    * 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 (source_parent_id, destination_parent_id) != 0 &&
-      g_strcmp0 (destination_basename, id) == 0)
+  if (g_strcmp0 (destination_basename, id) == 0)
     dummy_entry_filename = gdata_entry_get_title (source_entry);
   else
-    dummy_entry_filename = destination_basename;
+    {
+      /* 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,
@@ -1617,6 +1739,55 @@ g_vfs_backend_google_copy (GVfsBackend           *_self,
   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));
@@ -1654,8 +1825,8 @@ g_vfs_backend_google_move (GVfsBackend           *_self,
   GDataLink *source_parent_link = NULL;
   GDataLink *destination_parent_link = NULL;
   GError *error = NULL;
+  gboolean will_overwrite = FALSE;
   const gchar *source_id;
-  const gchar *volatile_entry_id;
   const gchar *source_parent_id;
   const gchar *destination_parent_id;
   const gchar *title;
@@ -1708,91 +1879,199 @@ g_vfs_backend_google_move (GVfsBackend           *_self,
   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);
 
-  /* We disallow over-writes to the same file in the same parent directory
-   * since Drive doesn't have any notion of "over-writing" a file. For the
-   * cases like renaming a file using `gio move id1 new_title`, we check using
-   * the below condition, and later update the title of the file conditionally. */
-  if (g_strcmp0 (source_parent_id, destination_parent_id) == 0 &&
-      g_strcmp0 (destination_basename, title) == 0)
+  existing_entry = resolve_child (self, destination_parent, destination_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;
-    }
+      const gchar *existing_entry_id;
+      gint64 num_same_title_files;
 
-  auth_domain = gdata_documents_service_get_primary_authorization_domain ();
+      existing_entry_id = gdata_entry_get_id (existing_entry);
+      num_same_title_files = count_files_in_directory_with_title (self,
+                                                                  destination_basename,
+                                                                  destination_parent);
 
-  existing_entry = resolve_child (self, destination_parent, source_id, cancellable, NULL);
-  if (existing_entry != NULL && g_strcmp0 (destination_parent_id, source_parent_id) != 0)
-    {
-      DirEntriesKey *key = dir_entries_key_new (source_id, destination_parent_id);
+      g_debug ("  count of files with title same as destination_basename: %ld\n", num_same_title_files);
 
-      /* We need to check if the real ID of source_entry being moved clashes with some,
-       * other entry's volatile ID or not. This happens when we're trying to move an entry
-       * but there already exists some tuple (source_id, destination_parent_id) in the
-       * dir_entries hash table. Here, the already present key corresponds to the
-       * volatile_entry_id of some previously copied file. This can happen for the
-       * following operations:
-       *
-       * 1. `gio copy ./id1 ./id2/`                   ("id2" is the real ID of a folder)
-       * 2. `gio move ./id1 ./id2/`
+      /* We don't support overwrites, so we don't need to care
+       * about G_IO_ERROR_IS_DIRECTORY and G_IO_ERROR_WOULD_MERGE.
        *
-       * The first copy operation will create a volatile entry in the
-       * cache i.e. (id1, id2) -> NewEntry. The subsequent move operation will see that there
-       * already exists a tuple (id1, id2), and hence this case will happen. If
-       * volatile_entry_id != NULL, we simply ignore the case and allow the operation
-       * to happen. */
-      if ((volatile_entry_id = get_volatile_entry_id (self, source_entry, key)) == NULL)
+       * 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)
         {
-          /* This case happens when a file has multiple parents and we're trying to
-           * perform a move operation from one parent to another parent (where the
-           * file already exists). Such operation simply doesn't makes sense
-           * since moving the same file here and there has no consequence.
-           * Hence, we return _EXISTS. */
-          g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
-          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 (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;
+            }
         }
     }
 
-  existing_entry = resolve_child (self, destination_parent, destination_basename, cancellable, NULL);
-  if (existing_entry != NULL)
+  /* 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)
     {
-      const gchar *existing_entry_id = gdata_entry_get_id (existing_entry);
-
-      /* 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.
-       *
-       * Doing the below check allows us to support the following sequence
-       * of operations -
-       * 1. `gio copy id1 id2/foo`
-       * 2. `gio move id1 id2/foo`
-       *
-       * Moreover, it disallows operations where foo is the real ID of some
-       * other file existing in folder with ID `id2`. Although, in-practive
-       * we can allow this operation, but later when query_info will be called,
-       * it will resolve to some other entry instead of the expected entry. */
-      if (g_strcmp0 (existing_entry_id, source_id) == 0 ||
-          g_strcmp0 (existing_entry_id, destination_basename) == 0)
+      existing_entry = resolve_child (self, destination_parent, title, cancellable, NULL);
+      if (existing_entry != NULL)
         {
-          /* The reason why we're returning G_IO_ERROR_FAILED here
-           * instead of G_IO_ERROR_NOT_SUPPORTED is because _NOT_SUPPORTED
-           * implies that this operation isn't implemented and it tells GIO
-           * to use some other fallback for it.
-           * So, for move operation, it'll use a copy+delete fallback here.
-           * Since, we don't support backups, so the fallback will end up
-           * removing the source file completely without making any copy, and
-           * we lose the file. Hence, we return G_IO_ERROR_FAILED.
-           */
-          g_vfs_job_failed_literal (G_VFS_JOB (job),
-                                    G_IO_ERROR,
-                                    G_IO_ERROR_FAILED,
-                                    _("Operation not supported"));
-          goto out;
+          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);
@@ -1823,7 +2102,13 @@ g_vfs_backend_google_move (GVfsBackend           *_self,
    * 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)
-    gdata_entry_set_title (source_entry, destination_basename);
+    {
+      /* 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),
@@ -1840,6 +2125,55 @@ g_vfs_backend_google_move (GVfsBackend           *_self,
       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);
 
@@ -2437,6 +2771,7 @@ g_vfs_backend_google_push (GVfsBackend           *_self,
   gchar *destination_basename = NULL;
   gchar *entry_path = NULL;
   gchar *parent_path = NULL;
+  gchar *local_file_title = NULL;
   goffset size;
 
   g_rec_mutex_lock (&self->mutex);
@@ -2469,6 +2804,8 @@ g_vfs_backend_google_push (GVfsBackend           *_self,
       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)
@@ -2481,8 +2818,19 @@ g_vfs_backend_google_push (GVfsBackend           *_self,
   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)
@@ -2522,12 +2870,40 @@ g_vfs_backend_google_push (GVfsBackend           *_self,
                 }
             }
 
-          needs_overwrite = TRUE;
+          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
         {
-          g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, G_IO_ERROR_EXISTS, _("Target file already exists"));
-          goto out;
+          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
@@ -2558,7 +2934,7 @@ g_vfs_backend_google_push (GVfsBackend           *_self,
   if (needs_overwrite)
     {
       document = GDATA_DOCUMENTS_DOCUMENT (g_object_ref (existing_entry));
-      title = gdata_entry_get_title (existing_entry);
+      title = local_file_title;
 
       error = NULL;
       ostream = gdata_documents_service_update_document (self->service,
@@ -2648,6 +3024,7 @@ g_vfs_backend_google_push (GVfsBackend           *_self,
   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");



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]