Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / drive / file_system / copy_operation.cc
index 0f0899a..2300c31 100644 (file)
 
 #include <string>
 
-#include "base/file_util.h"
 #include "base/task_runner_util.h"
 #include "chrome/browser/chromeos/drive/drive.pb.h"
 #include "chrome/browser/chromeos/drive/file_cache.h"
+#include "chrome/browser/chromeos/drive/file_change.h"
 #include "chrome/browser/chromeos/drive/file_system/create_file_operation.h"
-#include "chrome/browser/chromeos/drive/file_system/download_operation.h"
-#include "chrome/browser/chromeos/drive/file_system/move_operation.h"
-#include "chrome/browser/chromeos/drive/file_system/operation_observer.h"
+#include "chrome/browser/chromeos/drive/file_system/operation_delegate.h"
 #include "chrome/browser/chromeos/drive/file_system_util.h"
 #include "chrome/browser/chromeos/drive/job_scheduler.h"
 #include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
+#include "chrome/browser/chromeos/drive/resource_metadata.h"
 #include "chrome/browser/drive/drive_api_util.h"
 #include "content/public/browser/browser_thread.h"
+#include "google_apis/drive/drive_api_parser.h"
 
 using content::BrowserThread;
 
 namespace drive {
 namespace file_system {
 
+struct CopyOperation::CopyParams {
+  base::FilePath src_file_path;
+  base::FilePath dest_file_path;
+  bool preserve_last_modified;
+  FileOperationCallback callback;
+  ResourceEntry src_entry;
+  ResourceEntry parent_entry;
+};
+
+// Enum for categorizing where a gdoc represented by a JSON file exists.
+enum JsonGdocLocationType {
+  NOT_IN_METADATA,
+  IS_ORPHAN,
+  HAS_PARENT,
+};
+
+struct CopyOperation::TransferJsonGdocParams {
+  TransferJsonGdocParams(const FileOperationCallback& callback,
+                         const std::string& resource_id,
+                         const ResourceEntry& parent_entry,
+                         const std::string& new_title)
+      : callback(callback),
+        resource_id(resource_id),
+        parent_resource_id(parent_entry.resource_id()),
+        parent_local_id(parent_entry.local_id()),
+        new_title(new_title),
+        location_type(NOT_IN_METADATA) {
+  }
+  // Parameters supplied or calculated from operation arguments.
+  const FileOperationCallback callback;
+  const std::string resource_id;
+  const std::string parent_resource_id;
+  const std::string parent_local_id;
+  const std::string new_title;
+
+  // Values computed during operation.
+  JsonGdocLocationType location_type;  // types where the gdoc file is located.
+  std::string local_id;  // the local_id of the file (if exists in metadata.)
+  base::FilePath changed_path;
+};
+
 namespace {
 
-FileError PrepareCopy(internal::ResourceMetadata* metadata,
-                      const base::FilePath& src_path,
-                      const base::FilePath& dest_path,
-                      ResourceEntry* src_entry,
-                      std::string* parent_resource_id) {
-  FileError error = metadata->GetResourceEntryByPath(src_path, src_entry);
+FileError TryToCopyLocally(internal::ResourceMetadata* metadata,
+                           internal::FileCache* cache,
+                           CopyOperation::CopyParams* params,
+                           std::vector<std::string>* updated_local_ids,
+                           bool* directory_changed,
+                           bool* should_copy_on_server) {
+  FileError error = metadata->GetResourceEntryByPath(params->src_file_path,
+                                                     &params->src_entry);
   if (error != FILE_ERROR_OK)
     return error;
 
-  ResourceEntry parent_entry;
-  error = metadata->GetResourceEntryByPath(dest_path.DirName(), &parent_entry);
+  error = metadata->GetResourceEntryByPath(params->dest_file_path.DirName(),
+                                           &params->parent_entry);
   if (error != FILE_ERROR_OK)
     return error;
 
-  if (!parent_entry.file_info().is_directory())
+  if (!params->parent_entry.file_info().is_directory())
     return FILE_ERROR_NOT_A_DIRECTORY;
 
   // Drive File System doesn't support recursive copy.
-  if (src_entry->file_info().is_directory())
+  if (params->src_entry.file_info().is_directory())
     return FILE_ERROR_NOT_A_FILE;
 
-  *parent_resource_id = parent_entry.resource_id();
-  return FILE_ERROR_OK;
-}
+  // Check destination.
+  ResourceEntry dest_entry;
+  error = metadata->GetResourceEntryByPath(params->dest_file_path, &dest_entry);
+  switch (error) {
+    case FILE_ERROR_OK:
+      // File API spec says it is an error to try to "copy a file to a path
+      // occupied by a directory".
+      if (dest_entry.file_info().is_directory())
+        return FILE_ERROR_INVALID_OPERATION;
+
+      // Move the existing entry to the trash.
+      dest_entry.set_parent_local_id(util::kDriveTrashDirLocalId);
+      error = metadata->RefreshEntry(dest_entry);
+      if (error != FILE_ERROR_OK)
+        return error;
+      updated_local_ids->push_back(dest_entry.local_id());
+      *directory_changed = true;
+      break;
+    case FILE_ERROR_NOT_FOUND:
+      break;
+    default:
+      return error;
+  }
+
+  // If the cache file is not present and the entry exists on the server,
+  // server side copy should be used.
+  if (!params->src_entry.file_specific_info().cache_state().is_present() &&
+      !params->src_entry.resource_id().empty()) {
+    *should_copy_on_server = true;
+    return FILE_ERROR_OK;
+  }
+
+  // Copy locally.
+  ResourceEntry entry;
+  const int64 now = base::Time::Now().ToInternalValue();
+  entry.set_title(params->dest_file_path.BaseName().AsUTF8Unsafe());
+  entry.set_parent_local_id(params->parent_entry.local_id());
+  entry.mutable_file_specific_info()->set_content_mime_type(
+      params->src_entry.file_specific_info().content_mime_type());
+  entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+  entry.set_modification_date(base::Time::Now().ToInternalValue());
+  entry.mutable_file_info()->set_last_modified(
+      params->preserve_last_modified ?
+      params->src_entry.file_info().last_modified() : now);
+  entry.mutable_file_info()->set_last_accessed(now);
+
+  std::string local_id;
+  error = metadata->AddEntry(entry, &local_id);
+  if (error != FILE_ERROR_OK)
+    return error;
+  updated_local_ids->push_back(local_id);
+  *directory_changed = true;
+
+  if (!params->src_entry.file_specific_info().cache_state().is_present()) {
+    DCHECK(params->src_entry.resource_id().empty());
+    // Locally created empty file may have no cache file.
+    return FILE_ERROR_OK;
+  }
+
+  base::FilePath cache_file_path;
+  error = cache->GetFile(params->src_entry.local_id(), &cache_file_path);
+  if (error != FILE_ERROR_OK)
+    return error;
 
-int64 GetFileSize(const base::FilePath& file_path) {
-  int64 file_size;
-  if (!file_util::GetFileSize(file_path, &file_size))
-    return -1;
-  return file_size;
+  return cache->Store(local_id, std::string(), cache_file_path,
+                      internal::FileCache::FILE_OPERATION_COPY);
 }
 
-// Stores the copied entry and returns its path.
-FileError UpdateLocalStateForServerSideCopy(
+// Stores the entry returned from the server and returns its path.
+FileError UpdateLocalStateForServerSideOperation(
     internal::ResourceMetadata* metadata,
-    scoped_ptr<google_apis::ResourceEntry> resource_entry,
+    scoped_ptr<google_apis::FileResource> file_resource,
+    ResourceEntry* entry,
     base::FilePath* file_path) {
-  DCHECK(resource_entry);
+  DCHECK(file_resource);
 
-  ResourceEntry entry;
   std::string parent_resource_id;
-  if (!ConvertToResourceEntry(*resource_entry, &entry, &parent_resource_id))
+  if (!ConvertFileResourceToResourceEntry(
+          *file_resource, entry, &parent_resource_id) ||
+      parent_resource_id.empty())
     return FILE_ERROR_NOT_A_FILE;
 
   std::string parent_local_id;
@@ -76,19 +178,19 @@ FileError UpdateLocalStateForServerSideCopy(
                                                 &parent_local_id);
   if (error != FILE_ERROR_OK)
     return error;
-  entry.set_parent_local_id(parent_local_id);
+  entry->set_parent_local_id(parent_local_id);
 
   std::string local_id;
-  error = metadata->AddEntry(entry, &local_id);
+  error = metadata->AddEntry(*entry, &local_id);
   // Depending on timing, the metadata may have inserted via change list
   // already. So, FILE_ERROR_EXISTS is not an error.
   if (error == FILE_ERROR_EXISTS)
-    error = metadata->GetIdByResourceId(entry.resource_id(), &local_id);
+    error = metadata->GetIdByResourceId(entry->resource_id(), &local_id);
 
-  if (error == FILE_ERROR_OK)
-    *file_path = metadata->GetFilePath(local_id);
+  if (error != FILE_ERROR_OK)
+    return error;
 
-  return error;
+  return metadata->GetFilePath(local_id, file_path);
 }
 
 // Stores the file at |local_file_path| to the cache as a content of entry at
@@ -98,27 +200,18 @@ FileError UpdateLocalStateForScheduleTransfer(
     internal::FileCache* cache,
     const base::FilePath& local_src_path,
     const base::FilePath& remote_dest_path,
+    ResourceEntry* entry,
     std::string* local_id) {
   FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
   if (error != FILE_ERROR_OK)
     return error;
 
-  ResourceEntry entry;
-  error = metadata->GetResourceEntryById(*local_id, &entry);
-  if (error != FILE_ERROR_OK)
-    return error;
-
-  error = cache->Store(
-      *local_id, entry.file_specific_info().md5(), local_src_path,
-      internal::FileCache::FILE_OPERATION_COPY);
+  error = metadata->GetResourceEntryById(*local_id, entry);
   if (error != FILE_ERROR_OK)
     return error;
 
-  error = cache->MarkDirty(*local_id);
-  if (error != FILE_ERROR_OK)
-    return error;
-
-  return FILE_ERROR_OK;
+  return cache->Store(*local_id, std::string(), local_src_path,
+                      internal::FileCache::FILE_OPERATION_COPY);
 }
 
 // Gets the file size of the |local_path|, and the ResourceEntry for the parent
@@ -128,64 +221,72 @@ FileError PrepareTransferFileFromLocalToRemote(
     const base::FilePath& local_src_path,
     const base::FilePath& remote_dest_path,
     std::string* gdoc_resource_id,
-    std::string* parent_resource_id) {
-  ResourceEntry parent_entry;
+    ResourceEntry* parent_entry) {
   FileError error = metadata->GetResourceEntryByPath(
-      remote_dest_path.DirName(), &parent_entry);
+      remote_dest_path.DirName(), parent_entry);
   if (error != FILE_ERROR_OK)
     return error;
 
   // The destination's parent must be a directory.
-  if (!parent_entry.file_info().is_directory())
+  if (!parent_entry->file_info().is_directory())
     return FILE_ERROR_NOT_A_DIRECTORY;
 
   // Try to parse GDoc File and extract the resource id, if necessary.
   // Failing isn't problem. It'd be handled as a regular file, then.
-  if (util::HasGDocFileExtension(local_src_path)) {
+  if (util::HasHostedDocumentExtension(local_src_path))
     *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
-    *parent_resource_id = parent_entry.resource_id();
+  return FILE_ERROR_OK;
+}
+
+// Performs local work before server-side work for transferring JSON-represented
+// gdoc files.
+FileError LocalWorkForTransferJsonGdocFile(
+    internal::ResourceMetadata* metadata,
+    CopyOperation::TransferJsonGdocParams* params) {
+  std::string local_id;
+  FileError error = metadata->GetIdByResourceId(params->resource_id, &local_id);
+  if (error != FILE_ERROR_OK) {
+    params->location_type = NOT_IN_METADATA;
+    return error == FILE_ERROR_NOT_FOUND ? FILE_ERROR_OK : error;
+  }
+
+  ResourceEntry entry;
+  error = metadata->GetResourceEntryById(local_id, &entry);
+  if (error != FILE_ERROR_OK)
+    return error;
+  params->local_id = entry.local_id();
+
+  if (entry.parent_local_id() == util::kDriveOtherDirLocalId) {
+    params->location_type = IS_ORPHAN;
+    entry.set_title(params->new_title);
+    entry.set_parent_local_id(params->parent_local_id);
+    entry.set_metadata_edit_state(ResourceEntry::DIRTY);
+    entry.set_modification_date(base::Time::Now().ToInternalValue());
+    error = metadata->RefreshEntry(entry);
+    if (error != FILE_ERROR_OK)
+      return error;
+    return metadata->GetFilePath(local_id, &params->changed_path);
   }
 
+  params->location_type = HAS_PARENT;
   return FILE_ERROR_OK;
 }
 
 }  // namespace
 
-struct CopyOperation::CopyParams {
-  base::FilePath src_file_path;
-  base::FilePath dest_file_path;
-  bool preserve_last_modified;
-  FileOperationCallback callback;
-};
-
 CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
-                             OperationObserver* observer,
+                             OperationDelegate* delegate,
                              JobScheduler* scheduler,
                              internal::ResourceMetadata* metadata,
-                             internal::FileCache* cache,
-                             DriveServiceInterface* drive_service,
-                             const base::FilePath& temporary_file_directory)
+                             internal::FileCache* cache)
   : blocking_task_runner_(blocking_task_runner),
-    observer_(observer),
+    delegate_(delegate),
     scheduler_(scheduler),
     metadata_(metadata),
     cache_(cache),
-    drive_service_(drive_service),
     create_file_operation_(new CreateFileOperation(blocking_task_runner,
-                                                   observer,
-                                                   scheduler,
-                                                   metadata,
-                                                   cache)),
-    download_operation_(new DownloadOperation(blocking_task_runner,
-                                              observer,
-                                              scheduler,
-                                              metadata,
-                                              cache,
-                                              temporary_file_directory)),
-    move_operation_(new MoveOperation(blocking_task_runner,
-                                      observer,
-                                      scheduler,
-                                      metadata)),
+                                                   delegate,
+                                                   metadata)),
     weak_ptr_factory_(this) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 }
@@ -201,99 +302,112 @@ void CopyOperation::Copy(const base::FilePath& src_file_path,
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   DCHECK(!callback.is_null());
 
-  CopyParams params;
-  params.src_file_path = src_file_path;
-  params.dest_file_path = dest_file_path;
-  params.preserve_last_modified = preserve_last_modified;
-  params.callback = callback;
+  CopyParams* params = new CopyParams;
+  params->src_file_path = src_file_path;
+  params->dest_file_path = dest_file_path;
+  params->preserve_last_modified = preserve_last_modified;
+  params->callback = callback;
 
-  ResourceEntry* src_entry = new ResourceEntry;
-  std::string* parent_resource_id = new std::string;
+  std::vector<std::string>* updated_local_ids = new std::vector<std::string>;
+  bool* directory_changed = new bool(false);
+  bool* should_copy_on_server = new bool(false);
   base::PostTaskAndReplyWithResult(
       blocking_task_runner_.get(),
       FROM_HERE,
-      base::Bind(&PrepareCopy,
-                 metadata_, src_file_path, dest_file_path,
-                 src_entry, parent_resource_id),
-      base::Bind(&CopyOperation::CopyAfterPrepare,
-                 weak_ptr_factory_.GetWeakPtr(), params,
-                 base::Owned(src_entry), base::Owned(parent_resource_id)));
+      base::Bind(&TryToCopyLocally, metadata_, cache_, params,
+                 updated_local_ids, directory_changed, should_copy_on_server),
+      base::Bind(&CopyOperation::CopyAfterTryToCopyLocally,
+                 weak_ptr_factory_.GetWeakPtr(), base::Owned(params),
+                 base::Owned(updated_local_ids), base::Owned(directory_changed),
+                 base::Owned(should_copy_on_server)));
 }
 
-void CopyOperation::CopyAfterPrepare(
-    const CopyParams& params,
-    ResourceEntry* src_entry,
-    std::string* parent_resource_id,
+void CopyOperation::CopyAfterTryToCopyLocally(
+    const CopyParams* params,
+    const std::vector<std::string>* updated_local_ids,
+    const bool* directory_changed,
+    const bool* should_copy_on_server,
     FileError error) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  DCHECK(!params.callback.is_null());
+  DCHECK(!params->callback.is_null());
+
+  for (size_t i = 0; i < updated_local_ids->size(); ++i)
+    delegate_->OnEntryUpdatedByOperation((*updated_local_ids)[i]);
+
+  if (*directory_changed) {
+    FileChange changed_file;
+    DCHECK(!params->src_entry.file_info().is_directory());
+    changed_file.Update(params->dest_file_path,
+                        FileChange::FILE_TYPE_FILE,
+                        FileChange::ADD_OR_UPDATE);
+    delegate_->OnFileChangedByOperation(changed_file);
+  }
 
-  if (error != FILE_ERROR_OK) {
-    params.callback.Run(error);
+  if (error != FILE_ERROR_OK || !*should_copy_on_server) {
+    params->callback.Run(error);
     return;
   }
 
-  // If Drive API v2 is enabled, we can copy resources on server side.
-  if (util::IsDriveV2ApiEnabled()) {
-    base::FilePath new_title = params.dest_file_path.BaseName();
-    if (src_entry->file_specific_info().is_hosted_document()) {
-      // Drop the document extension, which should not be in the title.
-      // TODO(yoshiki): Remove this code with crbug.com/223304.
-      new_title = new_title.RemoveExtension();
-    }
-
-    base::Time last_modified =
-        params.preserve_last_modified ?
-        base::Time::FromInternalValue(src_entry->file_info().last_modified()) :
-        base::Time();
-
-    CopyResourceOnServer(
-        src_entry->resource_id(), *parent_resource_id,
-        new_title.AsUTF8Unsafe(), last_modified, params.callback);
-    return;
+  if (params->parent_entry.resource_id().empty()) {
+    // Parent entry may be being synced.
+    const bool waiting = delegate_->WaitForSyncComplete(
+        params->parent_entry.local_id(),
+        base::Bind(&CopyOperation::CopyAfterParentSync,
+                   weak_ptr_factory_.GetWeakPtr(), *params));
+    if (!waiting)
+      params->callback.Run(FILE_ERROR_NOT_FOUND);
+  } else {
+    CopyAfterGetParentResourceId(*params, &params->parent_entry, FILE_ERROR_OK);
   }
+}
+
+void CopyOperation::CopyAfterParentSync(const CopyParams& params,
+                                        FileError error) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+  DCHECK(!params.callback.is_null());
 
-  // Here after GData WAPI's code.
-  if (src_entry->file_specific_info().is_hosted_document()) {
-    // For hosted documents, GData WAPI copies it on server.
-    CopyHostedDocument(
-        src_entry->resource_id(),
-        // Drop the document extension, which should not be in the title.
-        // TODO(yoshiki): Remove this code with crbug.com/223304.
-        params.dest_file_path.RemoveExtension(),
-        params.callback);
+  if (error != FILE_ERROR_OK) {
+    params.callback.Run(error);
     return;
   }
 
-  // For regular files, download the content, and then re-upload.
-  // Note that upload is done later by SyncClient.
-  download_operation_->EnsureFileDownloadedByPath(
-      params.src_file_path,
-      ClientContext(USER_INITIATED),
-      GetFileContentInitializedCallback(),
-      google_apis::GetContentCallback(),
-      base::Bind(&CopyOperation::CopyAfterDownload,
-                 weak_ptr_factory_.GetWeakPtr(),
-                 params.dest_file_path, params.callback));
+  ResourceEntry* parent = new ResourceEntry;
+  base::PostTaskAndReplyWithResult(
+      blocking_task_runner_,
+      FROM_HERE,
+      base::Bind(&internal::ResourceMetadata::GetResourceEntryById,
+                 base::Unretained(metadata_),
+                 params.parent_entry.local_id(), parent),
+      base::Bind(&CopyOperation::CopyAfterGetParentResourceId,
+                 weak_ptr_factory_.GetWeakPtr(), params, base::Owned(parent)));
 }
 
-void CopyOperation::CopyAfterDownload(
-    const base::FilePath& dest_file_path,
-    const FileOperationCallback& callback,
-    FileError error,
-    const base::FilePath& local_file_path,
-    scoped_ptr<ResourceEntry> entry) {
+void CopyOperation::CopyAfterGetParentResourceId(const CopyParams& params,
+                                                 const ResourceEntry* parent,
+                                                 FileError error) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  DCHECK(!callback.is_null());
+  DCHECK(!params.callback.is_null());
 
   if (error != FILE_ERROR_OK) {
-    callback.Run(error);
+    params.callback.Run(error);
     return;
   }
 
-  // This callback is only triggered for a regular file via Copy().
-  DCHECK(entry && !entry->file_specific_info().is_hosted_document());
-  ScheduleTransferRegularFile(local_file_path, dest_file_path, callback);
+  base::FilePath new_title = params.dest_file_path.BaseName();
+  if (params.src_entry.file_specific_info().is_hosted_document()) {
+    // Drop the document extension, which should not be in the title.
+    // TODO(yoshiki): Remove this code with crbug.com/223304.
+    new_title = new_title.RemoveExtension();
+  }
+
+  base::Time last_modified =
+      params.preserve_last_modified ?
+      base::Time::FromInternalValue(
+          params.src_entry.file_info().last_modified()) : base::Time();
+
+  CopyResourceOnServer(
+      params.src_entry.resource_id(), parent->resource_id(),
+      new_title.AsUTF8Unsafe(), last_modified, params.callback);
 }
 
 void CopyOperation::TransferFileFromLocalToRemote(
@@ -304,19 +418,19 @@ void CopyOperation::TransferFileFromLocalToRemote(
   DCHECK(!callback.is_null());
 
   std::string* gdoc_resource_id = new std::string;
-  std::string* parent_resource_id = new std::string;
+  ResourceEntry* parent_entry = new ResourceEntry;
   base::PostTaskAndReplyWithResult(
       blocking_task_runner_.get(),
       FROM_HERE,
       base::Bind(
           &PrepareTransferFileFromLocalToRemote,
           metadata_, local_src_path, remote_dest_path,
-          gdoc_resource_id, parent_resource_id),
+          gdoc_resource_id, parent_entry),
       base::Bind(
           &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
           weak_ptr_factory_.GetWeakPtr(),
           local_src_path, remote_dest_path, callback,
-          base::Owned(gdoc_resource_id), base::Owned(parent_resource_id)));
+          base::Owned(gdoc_resource_id), base::Owned(parent_entry)));
 }
 
 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
@@ -324,7 +438,7 @@ void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
     const base::FilePath& remote_dest_path,
     const FileOperationCallback& callback,
     std::string* gdoc_resource_id,
-    std::string* parent_resource_id,
+    ResourceEntry* parent_entry,
     FileError error) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   DCHECK(!callback.is_null());
@@ -340,32 +454,80 @@ void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
     return;
   }
 
-  // This is uploading a JSON file representing a hosted document.
-  // Copy the document on the Drive server.
-
   // GDoc file may contain a resource ID in the old format.
   const std::string canonicalized_resource_id =
-      drive_service_->GetResourceIdCanonicalizer().Run(*gdoc_resource_id);
-
-  // If Drive API v2 is enabled, we can copy resources on server side.
-  if (util::IsDriveV2ApiEnabled()) {
-    CopyResourceOnServer(
-        *gdoc_resource_id, *parent_resource_id,
-        // Drop the document extension, which should not be in the title.
-        // TODO(yoshiki): Remove this code with crbug.com/223304.
-        remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe(),
-        base::Time(),
-        callback);
+      util::CanonicalizeResourceId(*gdoc_resource_id);
+
+  // Drop the document extension, which should not be in the title.
+  // TODO(yoshiki): Remove this code with crbug.com/223304.
+  const std::string new_title =
+      remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe();
+
+  // This is uploading a JSON file representing a hosted document.
+  TransferJsonGdocParams* params = new TransferJsonGdocParams(
+      callback, canonicalized_resource_id, *parent_entry, new_title);
+  base::PostTaskAndReplyWithResult(
+      blocking_task_runner_.get(),
+      FROM_HERE,
+      base::Bind(&LocalWorkForTransferJsonGdocFile, metadata_, params),
+      base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork,
+                 weak_ptr_factory_.GetWeakPtr(), base::Owned(params)));
+}
+
+void CopyOperation::TransferJsonGdocFileAfterLocalWork(
+    TransferJsonGdocParams* params,
+    FileError error) {
+  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+
+  if (error != FILE_ERROR_OK) {
+    params->callback.Run(error);
     return;
   }
 
-  CopyHostedDocument(
-      canonicalized_resource_id,
-      // Drop the document extension, which should not be
-      // in the document title.
-      // TODO(yoshiki): Remove this code with crbug.com/223304.
-      remote_dest_path.RemoveExtension(),
-      callback);
+  switch (params->location_type) {
+    // When |resource_id| is found in the local metadata and it has a specific
+    // parent folder, we assume the user's intention is to copy the document and
+    // thus perform the server-side copy operation.
+    case HAS_PARENT:
+      CopyResourceOnServer(params->resource_id,
+                           params->parent_resource_id,
+                           params->new_title,
+                           base::Time(),
+                           params->callback);
+      break;
+    // When |resource_id| has no parent, we just set the new destination folder
+    // as the parent, for sharing the document between the original source.
+    // This reparenting is already done in LocalWorkForTransferJsonGdocFile().
+    case IS_ORPHAN: {
+      DCHECK(!params->changed_path.empty());
+      delegate_->OnEntryUpdatedByOperation(params->local_id);
+
+      FileChange changed_file;
+      changed_file.Update(
+          params->changed_path,
+          FileChange::FILE_TYPE_FILE,  // This must be a hosted document.
+          FileChange::ADD_OR_UPDATE);
+      delegate_->OnFileChangedByOperation(changed_file);
+      params->callback.Run(error);
+      break;
+    }
+    // When the |resource_id| is not in the local metadata, assume it to be a
+    // document just now shared on the server but not synced locally.
+    // Same as the IS_ORPHAN case, we want to deal the case by setting parent,
+    // but this time we need to resort to server side operation.
+    case NOT_IN_METADATA:
+      scheduler_->UpdateResource(
+          params->resource_id,
+          params->parent_resource_id,
+          params->new_title,
+          base::Time(),
+          base::Time(),
+          ClientContext(USER_INITIATED),
+          base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     params->callback));
+      break;
+  }
 }
 
 void CopyOperation::CopyResourceOnServer(
@@ -379,15 +541,15 @@ void CopyOperation::CopyResourceOnServer(
 
   scheduler_->CopyResource(
       resource_id, parent_resource_id, new_title, last_modified,
-      base::Bind(&CopyOperation::CopyResourceOnServerAfterServerSideCopy,
+      base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
                  weak_ptr_factory_.GetWeakPtr(),
                  callback));
 }
 
-void CopyOperation::CopyResourceOnServerAfterServerSideCopy(
+void CopyOperation::UpdateAfterServerSideOperation(
     const FileOperationCallback& callback,
     google_apis::GDataErrorCode status,
-    scoped_ptr<google_apis::ResourceEntry> resource_entry) {
+    scoped_ptr<google_apis::FileResource> entry) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   DCHECK(!callback.is_null());
 
@@ -397,28 +559,39 @@ void CopyOperation::CopyResourceOnServerAfterServerSideCopy(
     return;
   }
 
+  ResourceEntry* resource_entry = new ResourceEntry;
+
   // The copy on the server side is completed successfully. Update the local
   // metadata.
   base::FilePath* file_path = new base::FilePath;
   base::PostTaskAndReplyWithResult(
       blocking_task_runner_.get(),
       FROM_HERE,
-      base::Bind(&UpdateLocalStateForServerSideCopy,
-                 metadata_, base::Passed(&resource_entry), file_path),
-      base::Bind(&CopyOperation::CopyResourceOnServerAfterUpdateLocalState,
+      base::Bind(&UpdateLocalStateForServerSideOperation,
+                 metadata_,
+                 base::Passed(&entry),
+                 resource_entry,
+                 file_path),
+      base::Bind(&CopyOperation::UpdateAfterLocalStateUpdate,
                  weak_ptr_factory_.GetWeakPtr(),
-                 callback, base::Owned(file_path)));
+                 callback,
+                 base::Owned(file_path),
+                 base::Owned(resource_entry)));
 }
 
-void CopyOperation::CopyResourceOnServerAfterUpdateLocalState(
+void CopyOperation::UpdateAfterLocalStateUpdate(
     const FileOperationCallback& callback,
     base::FilePath* file_path,
+    const ResourceEntry* entry,
     FileError error) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   DCHECK(!callback.is_null());
 
-  if (error == FILE_ERROR_OK)
-    observer_->OnDirectoryChangedByOperation(file_path->DirName());
+  if (error == FILE_ERROR_OK) {
+    FileChange changed_file;
+    changed_file.Update(*file_path, *entry, FileChange::ADD_OR_UPDATE);
+    delegate_->OnFileChangedByOperation(changed_file);
+  }
   callback.Run(error);
 }
 
@@ -429,64 +602,9 @@ void CopyOperation::ScheduleTransferRegularFile(
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   DCHECK(!callback.is_null());
 
-  base::PostTaskAndReplyWithResult(
-      blocking_task_runner_.get(),
-      FROM_HERE,
-      base::Bind(&GetFileSize, local_src_path),
-      base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterGetFileSize,
-                 weak_ptr_factory_.GetWeakPtr(),
-                 local_src_path, remote_dest_path, callback));
-}
-
-void CopyOperation::ScheduleTransferRegularFileAfterGetFileSize(
-    const base::FilePath& local_src_path,
-    const base::FilePath& remote_dest_path,
-    const FileOperationCallback& callback,
-    int64 local_file_size) {
-  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  DCHECK(!callback.is_null());
-
-  if (local_file_size < 0) {
-    callback.Run(FILE_ERROR_NOT_FOUND);
-    return;
-  }
-
-  // For regular files, check the server-side quota whether sufficient space
-  // is available for the file to be uploaded.
-  scheduler_->GetAboutResource(
-      base::Bind(
-          &CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource,
-          weak_ptr_factory_.GetWeakPtr(),
-          local_src_path, remote_dest_path, callback, local_file_size));
-}
-
-void CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource(
-    const base::FilePath& local_src_path,
-    const base::FilePath& remote_dest_path,
-    const FileOperationCallback& callback,
-    int64 local_file_size,
-    google_apis::GDataErrorCode status,
-    scoped_ptr<google_apis::AboutResource> about_resource) {
-  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  DCHECK(!callback.is_null());
-
-  FileError error = GDataToFileError(status);
-  if (error != FILE_ERROR_OK) {
-    callback.Run(error);
-    return;
-  }
-
-  DCHECK(about_resource);
-  const int64 space =
-      about_resource->quota_bytes_total() - about_resource->quota_bytes_used();
-  if (space < local_file_size) {
-    callback.Run(FILE_ERROR_NO_SERVER_SPACE);
-    return;
-  }
-
   create_file_operation_->CreateFile(
       remote_dest_path,
-      true,  // Exclusive (i.e. fail if a file already exists).
+      false,  // Not exclusive (OK even if a file already exists).
       std::string(),  // no specific mime type; CreateFile should guess it.
       base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
                  weak_ptr_factory_.GetWeakPtr(),
@@ -507,91 +625,42 @@ void CopyOperation::ScheduleTransferRegularFileAfterCreate(
   }
 
   std::string* local_id = new std::string;
+  ResourceEntry* entry = new ResourceEntry;
   base::PostTaskAndReplyWithResult(
       blocking_task_runner_.get(),
       FROM_HERE,
-      base::Bind(
-          &UpdateLocalStateForScheduleTransfer,
-          metadata_, cache_, local_src_path, remote_dest_path, local_id),
+      base::Bind(&UpdateLocalStateForScheduleTransfer,
+                 metadata_,
+                 cache_,
+                 local_src_path,
+                 remote_dest_path,
+                 entry,
+                 local_id),
       base::Bind(
           &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
-          weak_ptr_factory_.GetWeakPtr(), callback, base::Owned(local_id)));
+          weak_ptr_factory_.GetWeakPtr(),
+          callback,
+          remote_dest_path,
+          base::Owned(entry),
+          base::Owned(local_id)));
 }
 
 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
     const FileOperationCallback& callback,
+    const base::FilePath& remote_dest_path,
+    const ResourceEntry* entry,
     std::string* local_id,
     FileError error) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
   DCHECK(!callback.is_null());
 
-  if (error == FILE_ERROR_OK)
-    observer_->OnCacheFileUploadNeededByOperation(*local_id);
-  callback.Run(error);
-}
-
-void CopyOperation::CopyHostedDocument(
-    const std::string& resource_id,
-    const base::FilePath& dest_path,
-    const FileOperationCallback& callback) {
-  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  DCHECK(!callback.is_null());
-
-  scheduler_->CopyHostedDocument(
-      resource_id,
-      dest_path.BaseName().AsUTF8Unsafe(),
-      base::Bind(&CopyOperation::CopyHostedDocumentAfterServerSideCopy,
-                 weak_ptr_factory_.GetWeakPtr(), dest_path, callback));
-}
-
-void CopyOperation::CopyHostedDocumentAfterServerSideCopy(
-    const base::FilePath& dest_path,
-    const FileOperationCallback& callback,
-    google_apis::GDataErrorCode status,
-    scoped_ptr<google_apis::ResourceEntry> resource_entry) {
-  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  DCHECK(!callback.is_null());
-
-  FileError error = GDataToFileError(status);
-  if (error != FILE_ERROR_OK) {
-    callback.Run(error);
-    return;
-  }
-
-  // The document is copied into the root directory (temporarily) on the server,
-  // so we should first update the local metadata, then move it to the
-  // destination directory.
-  base::FilePath* file_path = new base::FilePath;
-  base::PostTaskAndReplyWithResult(
-      blocking_task_runner_.get(),
-      FROM_HERE,
-      base::Bind(&UpdateLocalStateForServerSideCopy,
-                 metadata_, base::Passed(&resource_entry), file_path),
-      base::Bind(&CopyOperation::CopyHostedDocumentAfterUpdateLocalState,
-                 weak_ptr_factory_.GetWeakPtr(),
-                 dest_path, callback, base::Owned(file_path)));
-}
-
-void CopyOperation::CopyHostedDocumentAfterUpdateLocalState(
-    const base::FilePath& dest_path,
-    const FileOperationCallback& callback,
-    base::FilePath* file_path,
-    FileError error) {
-  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  DCHECK(!callback.is_null());
-
-  // If an error is found, or the destination directory is mydrive root
-  // (i.e. it is unnecessary to move the copied document), return the status.
-  if (error != FILE_ERROR_OK ||
-      dest_path.DirName() == util::GetDriveMyDriveRootPath()) {
-    callback.Run(error);
-    return;
+  if (error == FILE_ERROR_OK) {
+    FileChange changed_file;
+    changed_file.Update(remote_dest_path, *entry, FileChange::ADD_OR_UPDATE);
+    delegate_->OnFileChangedByOperation(changed_file);
+    delegate_->OnEntryUpdatedByOperation(*local_id);
   }
-
-  DCHECK_EQ(util::GetDriveMyDriveRootPath().value(),
-            file_path->DirName().value()) << file_path->value();
-
-  move_operation_->Move(*file_path, dest_path, false, callback);
+  callback.Run(error);
 }
 
 }  // namespace file_system