1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/chromeos/drive/file_system/copy_operation.h"
6
7#include <string>
8
9#include "base/task_runner_util.h"
10#include "chrome/browser/chromeos/drive/drive.pb.h"
11#include "chrome/browser/chromeos/drive/file_cache.h"
12#include "chrome/browser/chromeos/drive/file_change.h"
13#include "chrome/browser/chromeos/drive/file_system/create_file_operation.h"
14#include "chrome/browser/chromeos/drive/file_system/operation_delegate.h"
15#include "chrome/browser/chromeos/drive/file_system_util.h"
16#include "chrome/browser/chromeos/drive/job_scheduler.h"
17#include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
18#include "chrome/browser/chromeos/drive/resource_metadata.h"
19#include "chrome/browser/drive/drive_api_util.h"
20#include "content/public/browser/browser_thread.h"
21#include "google_apis/drive/drive_api_parser.h"
22
23using content::BrowserThread;
24
25namespace drive {
26namespace file_system {
27
28struct CopyOperation::CopyParams {
29  base::FilePath src_file_path;
30  base::FilePath dest_file_path;
31  bool preserve_last_modified;
32  FileOperationCallback callback;
33  ResourceEntry src_entry;
34  ResourceEntry parent_entry;
35};
36
37// Enum for categorizing where a gdoc represented by a JSON file exists.
38enum JsonGdocLocationType {
39  NOT_IN_METADATA,
40  IS_ORPHAN,
41  HAS_PARENT,
42};
43
44struct CopyOperation::TransferJsonGdocParams {
45  TransferJsonGdocParams(const FileOperationCallback& callback,
46                         const std::string& resource_id,
47                         const ResourceEntry& parent_entry,
48                         const std::string& new_title)
49      : callback(callback),
50        resource_id(resource_id),
51        parent_resource_id(parent_entry.resource_id()),
52        parent_local_id(parent_entry.local_id()),
53        new_title(new_title),
54        location_type(NOT_IN_METADATA) {
55  }
56  // Parameters supplied or calculated from operation arguments.
57  const FileOperationCallback callback;
58  const std::string resource_id;
59  const std::string parent_resource_id;
60  const std::string parent_local_id;
61  const std::string new_title;
62
63  // Values computed during operation.
64  JsonGdocLocationType location_type;  // types where the gdoc file is located.
65  std::string local_id;  // the local_id of the file (if exists in metadata.)
66  base::FilePath changed_path;
67};
68
69namespace {
70
71FileError TryToCopyLocally(internal::ResourceMetadata* metadata,
72                           internal::FileCache* cache,
73                           CopyOperation::CopyParams* params,
74                           std::vector<std::string>* updated_local_ids,
75                           bool* directory_changed,
76                           bool* should_copy_on_server) {
77  FileError error = metadata->GetResourceEntryByPath(params->src_file_path,
78                                                     &params->src_entry);
79  if (error != FILE_ERROR_OK)
80    return error;
81
82  error = metadata->GetResourceEntryByPath(params->dest_file_path.DirName(),
83                                           &params->parent_entry);
84  if (error != FILE_ERROR_OK)
85    return error;
86
87  if (!params->parent_entry.file_info().is_directory())
88    return FILE_ERROR_NOT_A_DIRECTORY;
89
90  // Drive File System doesn't support recursive copy.
91  if (params->src_entry.file_info().is_directory())
92    return FILE_ERROR_NOT_A_FILE;
93
94  // Check destination.
95  ResourceEntry dest_entry;
96  error = metadata->GetResourceEntryByPath(params->dest_file_path, &dest_entry);
97  switch (error) {
98    case FILE_ERROR_OK:
99      // File API spec says it is an error to try to "copy a file to a path
100      // occupied by a directory".
101      if (dest_entry.file_info().is_directory())
102        return FILE_ERROR_INVALID_OPERATION;
103
104      // Move the existing entry to the trash.
105      dest_entry.set_parent_local_id(util::kDriveTrashDirLocalId);
106      error = metadata->RefreshEntry(dest_entry);
107      if (error != FILE_ERROR_OK)
108        return error;
109      updated_local_ids->push_back(dest_entry.local_id());
110      *directory_changed = true;
111      break;
112    case FILE_ERROR_NOT_FOUND:
113      break;
114    default:
115      return error;
116  }
117
118  // If the cache file is not present and the entry exists on the server,
119  // server side copy should be used.
120  if (!params->src_entry.file_specific_info().cache_state().is_present() &&
121      !params->src_entry.resource_id().empty()) {
122    *should_copy_on_server = true;
123    return FILE_ERROR_OK;
124  }
125
126  // Copy locally.
127  ResourceEntry entry;
128  const int64 now = base::Time::Now().ToInternalValue();
129  entry.set_title(params->dest_file_path.BaseName().AsUTF8Unsafe());
130  entry.set_parent_local_id(params->parent_entry.local_id());
131  entry.mutable_file_specific_info()->set_content_mime_type(
132      params->src_entry.file_specific_info().content_mime_type());
133  entry.set_metadata_edit_state(ResourceEntry::DIRTY);
134  entry.set_modification_date(base::Time::Now().ToInternalValue());
135  entry.mutable_file_info()->set_last_modified(
136      params->preserve_last_modified ?
137      params->src_entry.file_info().last_modified() : now);
138  entry.mutable_file_info()->set_last_accessed(now);
139
140  std::string local_id;
141  error = metadata->AddEntry(entry, &local_id);
142  if (error != FILE_ERROR_OK)
143    return error;
144  updated_local_ids->push_back(local_id);
145  *directory_changed = true;
146
147  if (!params->src_entry.file_specific_info().cache_state().is_present()) {
148    DCHECK(params->src_entry.resource_id().empty());
149    // Locally created empty file may have no cache file.
150    return FILE_ERROR_OK;
151  }
152
153  base::FilePath cache_file_path;
154  error = cache->GetFile(params->src_entry.local_id(), &cache_file_path);
155  if (error != FILE_ERROR_OK)
156    return error;
157
158  return cache->Store(local_id, std::string(), cache_file_path,
159                      internal::FileCache::FILE_OPERATION_COPY);
160}
161
162// Stores the entry returned from the server and returns its path.
163FileError UpdateLocalStateForServerSideOperation(
164    internal::ResourceMetadata* metadata,
165    scoped_ptr<google_apis::FileResource> file_resource,
166    ResourceEntry* entry,
167    base::FilePath* file_path) {
168  DCHECK(file_resource);
169
170  std::string parent_resource_id;
171  if (!ConvertFileResourceToResourceEntry(
172          *file_resource, entry, &parent_resource_id) ||
173      parent_resource_id.empty())
174    return FILE_ERROR_NOT_A_FILE;
175
176  std::string parent_local_id;
177  FileError error = metadata->GetIdByResourceId(parent_resource_id,
178                                                &parent_local_id);
179  if (error != FILE_ERROR_OK)
180    return error;
181  entry->set_parent_local_id(parent_local_id);
182
183  std::string local_id;
184  error = metadata->AddEntry(*entry, &local_id);
185  // Depending on timing, the metadata may have inserted via change list
186  // already. So, FILE_ERROR_EXISTS is not an error.
187  if (error == FILE_ERROR_EXISTS)
188    error = metadata->GetIdByResourceId(entry->resource_id(), &local_id);
189
190  if (error != FILE_ERROR_OK)
191    return error;
192
193  return metadata->GetFilePath(local_id, file_path);
194}
195
196// Stores the file at |local_file_path| to the cache as a content of entry at
197// |remote_dest_path|, and marks it dirty.
198FileError UpdateLocalStateForScheduleTransfer(
199    internal::ResourceMetadata* metadata,
200    internal::FileCache* cache,
201    const base::FilePath& local_src_path,
202    const base::FilePath& remote_dest_path,
203    ResourceEntry* entry,
204    std::string* local_id) {
205  FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
206  if (error != FILE_ERROR_OK)
207    return error;
208
209  error = metadata->GetResourceEntryById(*local_id, entry);
210  if (error != FILE_ERROR_OK)
211    return error;
212
213  return cache->Store(*local_id, std::string(), local_src_path,
214                      internal::FileCache::FILE_OPERATION_COPY);
215}
216
217// Gets the file size of the |local_path|, and the ResourceEntry for the parent
218// of |remote_path| to prepare the necessary information for transfer.
219FileError PrepareTransferFileFromLocalToRemote(
220    internal::ResourceMetadata* metadata,
221    const base::FilePath& local_src_path,
222    const base::FilePath& remote_dest_path,
223    std::string* gdoc_resource_id,
224    ResourceEntry* parent_entry) {
225  FileError error = metadata->GetResourceEntryByPath(
226      remote_dest_path.DirName(), parent_entry);
227  if (error != FILE_ERROR_OK)
228    return error;
229
230  // The destination's parent must be a directory.
231  if (!parent_entry->file_info().is_directory())
232    return FILE_ERROR_NOT_A_DIRECTORY;
233
234  // Try to parse GDoc File and extract the resource id, if necessary.
235  // Failing isn't problem. It'd be handled as a regular file, then.
236  if (util::HasHostedDocumentExtension(local_src_path))
237    *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
238  return FILE_ERROR_OK;
239}
240
241// Performs local work before server-side work for transferring JSON-represented
242// gdoc files.
243FileError LocalWorkForTransferJsonGdocFile(
244    internal::ResourceMetadata* metadata,
245    CopyOperation::TransferJsonGdocParams* params) {
246  std::string local_id;
247  FileError error = metadata->GetIdByResourceId(params->resource_id, &local_id);
248  if (error != FILE_ERROR_OK) {
249    params->location_type = NOT_IN_METADATA;
250    return error == FILE_ERROR_NOT_FOUND ? FILE_ERROR_OK : error;
251  }
252
253  ResourceEntry entry;
254  error = metadata->GetResourceEntryById(local_id, &entry);
255  if (error != FILE_ERROR_OK)
256    return error;
257  params->local_id = entry.local_id();
258
259  if (entry.parent_local_id() == util::kDriveOtherDirLocalId) {
260    params->location_type = IS_ORPHAN;
261    entry.set_title(params->new_title);
262    entry.set_parent_local_id(params->parent_local_id);
263    entry.set_metadata_edit_state(ResourceEntry::DIRTY);
264    entry.set_modification_date(base::Time::Now().ToInternalValue());
265    error = metadata->RefreshEntry(entry);
266    if (error != FILE_ERROR_OK)
267      return error;
268    return metadata->GetFilePath(local_id, &params->changed_path);
269  }
270
271  params->location_type = HAS_PARENT;
272  return FILE_ERROR_OK;
273}
274
275}  // namespace
276
277CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
278                             OperationDelegate* delegate,
279                             JobScheduler* scheduler,
280                             internal::ResourceMetadata* metadata,
281                             internal::FileCache* cache)
282  : blocking_task_runner_(blocking_task_runner),
283    delegate_(delegate),
284    scheduler_(scheduler),
285    metadata_(metadata),
286    cache_(cache),
287    create_file_operation_(new CreateFileOperation(blocking_task_runner,
288                                                   delegate,
289                                                   metadata)),
290    weak_ptr_factory_(this) {
291  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
292}
293
294CopyOperation::~CopyOperation() {
295  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
296}
297
298void CopyOperation::Copy(const base::FilePath& src_file_path,
299                         const base::FilePath& dest_file_path,
300                         bool preserve_last_modified,
301                         const FileOperationCallback& callback) {
302  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
303  DCHECK(!callback.is_null());
304
305  CopyParams* params = new CopyParams;
306  params->src_file_path = src_file_path;
307  params->dest_file_path = dest_file_path;
308  params->preserve_last_modified = preserve_last_modified;
309  params->callback = callback;
310
311  std::vector<std::string>* updated_local_ids = new std::vector<std::string>;
312  bool* directory_changed = new bool(false);
313  bool* should_copy_on_server = new bool(false);
314  base::PostTaskAndReplyWithResult(
315      blocking_task_runner_.get(),
316      FROM_HERE,
317      base::Bind(&TryToCopyLocally, metadata_, cache_, params,
318                 updated_local_ids, directory_changed, should_copy_on_server),
319      base::Bind(&CopyOperation::CopyAfterTryToCopyLocally,
320                 weak_ptr_factory_.GetWeakPtr(), base::Owned(params),
321                 base::Owned(updated_local_ids), base::Owned(directory_changed),
322                 base::Owned(should_copy_on_server)));
323}
324
325void CopyOperation::CopyAfterTryToCopyLocally(
326    const CopyParams* params,
327    const std::vector<std::string>* updated_local_ids,
328    const bool* directory_changed,
329    const bool* should_copy_on_server,
330    FileError error) {
331  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
332  DCHECK(!params->callback.is_null());
333
334  for (size_t i = 0; i < updated_local_ids->size(); ++i)
335    delegate_->OnEntryUpdatedByOperation((*updated_local_ids)[i]);
336
337  if (*directory_changed) {
338    FileChange changed_file;
339    DCHECK(!params->src_entry.file_info().is_directory());
340    changed_file.Update(params->dest_file_path,
341                        FileChange::FILE_TYPE_FILE,
342                        FileChange::ADD_OR_UPDATE);
343    delegate_->OnFileChangedByOperation(changed_file);
344  }
345
346  if (error != FILE_ERROR_OK || !*should_copy_on_server) {
347    params->callback.Run(error);
348    return;
349  }
350
351  if (params->parent_entry.resource_id().empty()) {
352    // Parent entry may be being synced.
353    const bool waiting = delegate_->WaitForSyncComplete(
354        params->parent_entry.local_id(),
355        base::Bind(&CopyOperation::CopyAfterParentSync,
356                   weak_ptr_factory_.GetWeakPtr(), *params));
357    if (!waiting)
358      params->callback.Run(FILE_ERROR_NOT_FOUND);
359  } else {
360    CopyAfterGetParentResourceId(*params, &params->parent_entry, FILE_ERROR_OK);
361  }
362}
363
364void CopyOperation::CopyAfterParentSync(const CopyParams& params,
365                                        FileError error) {
366  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
367  DCHECK(!params.callback.is_null());
368
369  if (error != FILE_ERROR_OK) {
370    params.callback.Run(error);
371    return;
372  }
373
374  ResourceEntry* parent = new ResourceEntry;
375  base::PostTaskAndReplyWithResult(
376      blocking_task_runner_.get(),
377      FROM_HERE,
378      base::Bind(&internal::ResourceMetadata::GetResourceEntryById,
379                 base::Unretained(metadata_),
380                 params.parent_entry.local_id(),
381                 parent),
382      base::Bind(&CopyOperation::CopyAfterGetParentResourceId,
383                 weak_ptr_factory_.GetWeakPtr(),
384                 params,
385                 base::Owned(parent)));
386}
387
388void CopyOperation::CopyAfterGetParentResourceId(const CopyParams& params,
389                                                 const ResourceEntry* parent,
390                                                 FileError error) {
391  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
392  DCHECK(!params.callback.is_null());
393
394  if (error != FILE_ERROR_OK) {
395    params.callback.Run(error);
396    return;
397  }
398
399  base::FilePath new_title = params.dest_file_path.BaseName();
400  if (params.src_entry.file_specific_info().is_hosted_document()) {
401    // Drop the document extension, which should not be in the title.
402    // TODO(yoshiki): Remove this code with crbug.com/223304.
403    new_title = new_title.RemoveExtension();
404  }
405
406  base::Time last_modified =
407      params.preserve_last_modified ?
408      base::Time::FromInternalValue(
409          params.src_entry.file_info().last_modified()) : base::Time();
410
411  CopyResourceOnServer(
412      params.src_entry.resource_id(), parent->resource_id(),
413      new_title.AsUTF8Unsafe(), last_modified, params.callback);
414}
415
416void CopyOperation::TransferFileFromLocalToRemote(
417    const base::FilePath& local_src_path,
418    const base::FilePath& remote_dest_path,
419    const FileOperationCallback& callback) {
420  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
421  DCHECK(!callback.is_null());
422
423  std::string* gdoc_resource_id = new std::string;
424  ResourceEntry* parent_entry = new ResourceEntry;
425  base::PostTaskAndReplyWithResult(
426      blocking_task_runner_.get(),
427      FROM_HERE,
428      base::Bind(
429          &PrepareTransferFileFromLocalToRemote,
430          metadata_, local_src_path, remote_dest_path,
431          gdoc_resource_id, parent_entry),
432      base::Bind(
433          &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
434          weak_ptr_factory_.GetWeakPtr(),
435          local_src_path, remote_dest_path, callback,
436          base::Owned(gdoc_resource_id), base::Owned(parent_entry)));
437}
438
439void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
440    const base::FilePath& local_src_path,
441    const base::FilePath& remote_dest_path,
442    const FileOperationCallback& callback,
443    std::string* gdoc_resource_id,
444    ResourceEntry* parent_entry,
445    FileError error) {
446  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
447  DCHECK(!callback.is_null());
448
449  if (error != FILE_ERROR_OK) {
450    callback.Run(error);
451    return;
452  }
453
454  // For regular files, schedule the transfer.
455  if (gdoc_resource_id->empty()) {
456    ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
457    return;
458  }
459
460  // GDoc file may contain a resource ID in the old format.
461  const std::string canonicalized_resource_id =
462      util::CanonicalizeResourceId(*gdoc_resource_id);
463
464  // Drop the document extension, which should not be in the title.
465  // TODO(yoshiki): Remove this code with crbug.com/223304.
466  const std::string new_title =
467      remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe();
468
469  // This is uploading a JSON file representing a hosted document.
470  TransferJsonGdocParams* params = new TransferJsonGdocParams(
471      callback, canonicalized_resource_id, *parent_entry, new_title);
472  base::PostTaskAndReplyWithResult(
473      blocking_task_runner_.get(),
474      FROM_HERE,
475      base::Bind(&LocalWorkForTransferJsonGdocFile, metadata_, params),
476      base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork,
477                 weak_ptr_factory_.GetWeakPtr(), base::Owned(params)));
478}
479
480void CopyOperation::TransferJsonGdocFileAfterLocalWork(
481    TransferJsonGdocParams* params,
482    FileError error) {
483  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
484
485  if (error != FILE_ERROR_OK) {
486    params->callback.Run(error);
487    return;
488  }
489
490  switch (params->location_type) {
491    // When |resource_id| is found in the local metadata and it has a specific
492    // parent folder, we assume the user's intention is to copy the document and
493    // thus perform the server-side copy operation.
494    case HAS_PARENT:
495      CopyResourceOnServer(params->resource_id,
496                           params->parent_resource_id,
497                           params->new_title,
498                           base::Time(),
499                           params->callback);
500      break;
501    // When |resource_id| has no parent, we just set the new destination folder
502    // as the parent, for sharing the document between the original source.
503    // This reparenting is already done in LocalWorkForTransferJsonGdocFile().
504    case IS_ORPHAN: {
505      DCHECK(!params->changed_path.empty());
506      delegate_->OnEntryUpdatedByOperation(params->local_id);
507
508      FileChange changed_file;
509      changed_file.Update(
510          params->changed_path,
511          FileChange::FILE_TYPE_FILE,  // This must be a hosted document.
512          FileChange::ADD_OR_UPDATE);
513      delegate_->OnFileChangedByOperation(changed_file);
514      params->callback.Run(error);
515      break;
516    }
517    // When the |resource_id| is not in the local metadata, assume it to be a
518    // document just now shared on the server but not synced locally.
519    // Same as the IS_ORPHAN case, we want to deal the case by setting parent,
520    // but this time we need to resort to server side operation.
521    case NOT_IN_METADATA:
522      scheduler_->UpdateResource(
523          params->resource_id,
524          params->parent_resource_id,
525          params->new_title,
526          base::Time(),
527          base::Time(),
528          ClientContext(USER_INITIATED),
529          base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
530                     weak_ptr_factory_.GetWeakPtr(),
531                     params->callback));
532      break;
533  }
534}
535
536void CopyOperation::CopyResourceOnServer(
537    const std::string& resource_id,
538    const std::string& parent_resource_id,
539    const std::string& new_title,
540    const base::Time& last_modified,
541    const FileOperationCallback& callback) {
542  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
543  DCHECK(!callback.is_null());
544
545  scheduler_->CopyResource(
546      resource_id, parent_resource_id, new_title, last_modified,
547      base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
548                 weak_ptr_factory_.GetWeakPtr(),
549                 callback));
550}
551
552void CopyOperation::UpdateAfterServerSideOperation(
553    const FileOperationCallback& callback,
554    google_apis::GDataErrorCode status,
555    scoped_ptr<google_apis::FileResource> entry) {
556  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
557  DCHECK(!callback.is_null());
558
559  FileError error = GDataToFileError(status);
560  if (error != FILE_ERROR_OK) {
561    callback.Run(error);
562    return;
563  }
564
565  ResourceEntry* resource_entry = new ResourceEntry;
566
567  // The copy on the server side is completed successfully. Update the local
568  // metadata.
569  base::FilePath* file_path = new base::FilePath;
570  base::PostTaskAndReplyWithResult(
571      blocking_task_runner_.get(),
572      FROM_HERE,
573      base::Bind(&UpdateLocalStateForServerSideOperation,
574                 metadata_,
575                 base::Passed(&entry),
576                 resource_entry,
577                 file_path),
578      base::Bind(&CopyOperation::UpdateAfterLocalStateUpdate,
579                 weak_ptr_factory_.GetWeakPtr(),
580                 callback,
581                 base::Owned(file_path),
582                 base::Owned(resource_entry)));
583}
584
585void CopyOperation::UpdateAfterLocalStateUpdate(
586    const FileOperationCallback& callback,
587    base::FilePath* file_path,
588    const ResourceEntry* entry,
589    FileError error) {
590  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
591  DCHECK(!callback.is_null());
592
593  if (error == FILE_ERROR_OK) {
594    FileChange changed_file;
595    changed_file.Update(*file_path, *entry, FileChange::ADD_OR_UPDATE);
596    delegate_->OnFileChangedByOperation(changed_file);
597  }
598  callback.Run(error);
599}
600
601void CopyOperation::ScheduleTransferRegularFile(
602    const base::FilePath& local_src_path,
603    const base::FilePath& remote_dest_path,
604    const FileOperationCallback& callback) {
605  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
606  DCHECK(!callback.is_null());
607
608  create_file_operation_->CreateFile(
609      remote_dest_path,
610      false,  // Not exclusive (OK even if a file already exists).
611      std::string(),  // no specific mime type; CreateFile should guess it.
612      base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
613                 weak_ptr_factory_.GetWeakPtr(),
614                 local_src_path, remote_dest_path, callback));
615}
616
617void CopyOperation::ScheduleTransferRegularFileAfterCreate(
618    const base::FilePath& local_src_path,
619    const base::FilePath& remote_dest_path,
620    const FileOperationCallback& callback,
621    FileError error) {
622  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
623  DCHECK(!callback.is_null());
624
625  if (error != FILE_ERROR_OK) {
626    callback.Run(error);
627    return;
628  }
629
630  std::string* local_id = new std::string;
631  ResourceEntry* entry = new ResourceEntry;
632  base::PostTaskAndReplyWithResult(
633      blocking_task_runner_.get(),
634      FROM_HERE,
635      base::Bind(&UpdateLocalStateForScheduleTransfer,
636                 metadata_,
637                 cache_,
638                 local_src_path,
639                 remote_dest_path,
640                 entry,
641                 local_id),
642      base::Bind(
643          &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
644          weak_ptr_factory_.GetWeakPtr(),
645          callback,
646          remote_dest_path,
647          base::Owned(entry),
648          base::Owned(local_id)));
649}
650
651void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
652    const FileOperationCallback& callback,
653    const base::FilePath& remote_dest_path,
654    const ResourceEntry* entry,
655    std::string* local_id,
656    FileError error) {
657  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
658  DCHECK(!callback.is_null());
659
660  if (error == FILE_ERROR_OK) {
661    FileChange changed_file;
662    changed_file.Update(remote_dest_path, *entry, FileChange::ADD_OR_UPDATE);
663    delegate_->OnFileChangedByOperation(changed_file);
664    delegate_->OnEntryUpdatedByOperation(*local_id);
665  }
666  callback.Run(error);
667}
668
669}  // namespace file_system
670}  // namespace drive
671