copy_operation.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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_,
377      FROM_HERE,
378      base::Bind(&internal::ResourceMetadata::GetResourceEntryById,
379                 base::Unretained(metadata_),
380                 params.parent_entry.local_id(), parent),
381      base::Bind(&CopyOperation::CopyAfterGetParentResourceId,
382                 weak_ptr_factory_.GetWeakPtr(), params, base::Owned(parent)));
383}
384
385void CopyOperation::CopyAfterGetParentResourceId(const CopyParams& params,
386                                                 const ResourceEntry* parent,
387                                                 FileError error) {
388  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
389  DCHECK(!params.callback.is_null());
390
391  if (error != FILE_ERROR_OK) {
392    params.callback.Run(error);
393    return;
394  }
395
396  base::FilePath new_title = params.dest_file_path.BaseName();
397  if (params.src_entry.file_specific_info().is_hosted_document()) {
398    // Drop the document extension, which should not be in the title.
399    // TODO(yoshiki): Remove this code with crbug.com/223304.
400    new_title = new_title.RemoveExtension();
401  }
402
403  base::Time last_modified =
404      params.preserve_last_modified ?
405      base::Time::FromInternalValue(
406          params.src_entry.file_info().last_modified()) : base::Time();
407
408  CopyResourceOnServer(
409      params.src_entry.resource_id(), parent->resource_id(),
410      new_title.AsUTF8Unsafe(), last_modified, params.callback);
411}
412
413void CopyOperation::TransferFileFromLocalToRemote(
414    const base::FilePath& local_src_path,
415    const base::FilePath& remote_dest_path,
416    const FileOperationCallback& callback) {
417  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
418  DCHECK(!callback.is_null());
419
420  std::string* gdoc_resource_id = new std::string;
421  ResourceEntry* parent_entry = new ResourceEntry;
422  base::PostTaskAndReplyWithResult(
423      blocking_task_runner_.get(),
424      FROM_HERE,
425      base::Bind(
426          &PrepareTransferFileFromLocalToRemote,
427          metadata_, local_src_path, remote_dest_path,
428          gdoc_resource_id, parent_entry),
429      base::Bind(
430          &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
431          weak_ptr_factory_.GetWeakPtr(),
432          local_src_path, remote_dest_path, callback,
433          base::Owned(gdoc_resource_id), base::Owned(parent_entry)));
434}
435
436void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
437    const base::FilePath& local_src_path,
438    const base::FilePath& remote_dest_path,
439    const FileOperationCallback& callback,
440    std::string* gdoc_resource_id,
441    ResourceEntry* parent_entry,
442    FileError error) {
443  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
444  DCHECK(!callback.is_null());
445
446  if (error != FILE_ERROR_OK) {
447    callback.Run(error);
448    return;
449  }
450
451  // For regular files, schedule the transfer.
452  if (gdoc_resource_id->empty()) {
453    ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
454    return;
455  }
456
457  // GDoc file may contain a resource ID in the old format.
458  const std::string canonicalized_resource_id =
459      util::CanonicalizeResourceId(*gdoc_resource_id);
460
461  // Drop the document extension, which should not be in the title.
462  // TODO(yoshiki): Remove this code with crbug.com/223304.
463  const std::string new_title =
464      remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe();
465
466  // This is uploading a JSON file representing a hosted document.
467  TransferJsonGdocParams* params = new TransferJsonGdocParams(
468      callback, canonicalized_resource_id, *parent_entry, new_title);
469  base::PostTaskAndReplyWithResult(
470      blocking_task_runner_.get(),
471      FROM_HERE,
472      base::Bind(&LocalWorkForTransferJsonGdocFile, metadata_, params),
473      base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork,
474                 weak_ptr_factory_.GetWeakPtr(), base::Owned(params)));
475}
476
477void CopyOperation::TransferJsonGdocFileAfterLocalWork(
478    TransferJsonGdocParams* params,
479    FileError error) {
480  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
481
482  if (error != FILE_ERROR_OK) {
483    params->callback.Run(error);
484    return;
485  }
486
487  switch (params->location_type) {
488    // When |resource_id| is found in the local metadata and it has a specific
489    // parent folder, we assume the user's intention is to copy the document and
490    // thus perform the server-side copy operation.
491    case HAS_PARENT:
492      CopyResourceOnServer(params->resource_id,
493                           params->parent_resource_id,
494                           params->new_title,
495                           base::Time(),
496                           params->callback);
497      break;
498    // When |resource_id| has no parent, we just set the new destination folder
499    // as the parent, for sharing the document between the original source.
500    // This reparenting is already done in LocalWorkForTransferJsonGdocFile().
501    case IS_ORPHAN: {
502      DCHECK(!params->changed_path.empty());
503      delegate_->OnEntryUpdatedByOperation(params->local_id);
504
505      FileChange changed_file;
506      changed_file.Update(
507          params->changed_path,
508          FileChange::FILE_TYPE_FILE,  // This must be a hosted document.
509          FileChange::ADD_OR_UPDATE);
510      delegate_->OnFileChangedByOperation(changed_file);
511      params->callback.Run(error);
512      break;
513    }
514    // When the |resource_id| is not in the local metadata, assume it to be a
515    // document just now shared on the server but not synced locally.
516    // Same as the IS_ORPHAN case, we want to deal the case by setting parent,
517    // but this time we need to resort to server side operation.
518    case NOT_IN_METADATA:
519      scheduler_->UpdateResource(
520          params->resource_id,
521          params->parent_resource_id,
522          params->new_title,
523          base::Time(),
524          base::Time(),
525          ClientContext(USER_INITIATED),
526          base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
527                     weak_ptr_factory_.GetWeakPtr(),
528                     params->callback));
529      break;
530  }
531}
532
533void CopyOperation::CopyResourceOnServer(
534    const std::string& resource_id,
535    const std::string& parent_resource_id,
536    const std::string& new_title,
537    const base::Time& last_modified,
538    const FileOperationCallback& callback) {
539  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
540  DCHECK(!callback.is_null());
541
542  scheduler_->CopyResource(
543      resource_id, parent_resource_id, new_title, last_modified,
544      base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
545                 weak_ptr_factory_.GetWeakPtr(),
546                 callback));
547}
548
549void CopyOperation::UpdateAfterServerSideOperation(
550    const FileOperationCallback& callback,
551    google_apis::GDataErrorCode status,
552    scoped_ptr<google_apis::FileResource> entry) {
553  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
554  DCHECK(!callback.is_null());
555
556  FileError error = GDataToFileError(status);
557  if (error != FILE_ERROR_OK) {
558    callback.Run(error);
559    return;
560  }
561
562  ResourceEntry* resource_entry = new ResourceEntry;
563
564  // The copy on the server side is completed successfully. Update the local
565  // metadata.
566  base::FilePath* file_path = new base::FilePath;
567  base::PostTaskAndReplyWithResult(
568      blocking_task_runner_.get(),
569      FROM_HERE,
570      base::Bind(&UpdateLocalStateForServerSideOperation,
571                 metadata_,
572                 base::Passed(&entry),
573                 resource_entry,
574                 file_path),
575      base::Bind(&CopyOperation::UpdateAfterLocalStateUpdate,
576                 weak_ptr_factory_.GetWeakPtr(),
577                 callback,
578                 base::Owned(file_path),
579                 base::Owned(resource_entry)));
580}
581
582void CopyOperation::UpdateAfterLocalStateUpdate(
583    const FileOperationCallback& callback,
584    base::FilePath* file_path,
585    const ResourceEntry* entry,
586    FileError error) {
587  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
588  DCHECK(!callback.is_null());
589
590  if (error == FILE_ERROR_OK) {
591    FileChange changed_file;
592    changed_file.Update(*file_path, *entry, FileChange::ADD_OR_UPDATE);
593    delegate_->OnFileChangedByOperation(changed_file);
594  }
595  callback.Run(error);
596}
597
598void CopyOperation::ScheduleTransferRegularFile(
599    const base::FilePath& local_src_path,
600    const base::FilePath& remote_dest_path,
601    const FileOperationCallback& callback) {
602  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
603  DCHECK(!callback.is_null());
604
605  create_file_operation_->CreateFile(
606      remote_dest_path,
607      false,  // Not exclusive (OK even if a file already exists).
608      std::string(),  // no specific mime type; CreateFile should guess it.
609      base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
610                 weak_ptr_factory_.GetWeakPtr(),
611                 local_src_path, remote_dest_path, callback));
612}
613
614void CopyOperation::ScheduleTransferRegularFileAfterCreate(
615    const base::FilePath& local_src_path,
616    const base::FilePath& remote_dest_path,
617    const FileOperationCallback& callback,
618    FileError error) {
619  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
620  DCHECK(!callback.is_null());
621
622  if (error != FILE_ERROR_OK) {
623    callback.Run(error);
624    return;
625  }
626
627  std::string* local_id = new std::string;
628  ResourceEntry* entry = new ResourceEntry;
629  base::PostTaskAndReplyWithResult(
630      blocking_task_runner_.get(),
631      FROM_HERE,
632      base::Bind(&UpdateLocalStateForScheduleTransfer,
633                 metadata_,
634                 cache_,
635                 local_src_path,
636                 remote_dest_path,
637                 entry,
638                 local_id),
639      base::Bind(
640          &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
641          weak_ptr_factory_.GetWeakPtr(),
642          callback,
643          remote_dest_path,
644          base::Owned(entry),
645          base::Owned(local_id)));
646}
647
648void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
649    const FileOperationCallback& callback,
650    const base::FilePath& remote_dest_path,
651    const ResourceEntry* entry,
652    std::string* local_id,
653    FileError error) {
654  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
655  DCHECK(!callback.is_null());
656
657  if (error == FILE_ERROR_OK) {
658    FileChange changed_file;
659    changed_file.Update(remote_dest_path, *entry, FileChange::ADD_OR_UPDATE);
660    delegate_->OnFileChangedByOperation(changed_file);
661    delegate_->OnEntryUpdatedByOperation(*local_id);
662  }
663  callback.Run(error);
664}
665
666}  // namespace file_system
667}  // namespace drive
668