BookmarkManager: Fix 'new folder text field size changes on clicking it' issue.
[chromium-blink-merge.git] / components / drive / file_system / copy_operation.cc
blob595e8e1b1f6a238c0d202514b5a8f1af4abc1dc8
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.
5 #include "components/drive/file_system/copy_operation.h"
7 #include <string>
9 #include "base/task_runner_util.h"
10 #include "components/drive/drive.pb.h"
11 #include "components/drive/drive_api_util.h"
12 #include "components/drive/file_cache.h"
13 #include "components/drive/file_change.h"
14 #include "components/drive/file_system/create_file_operation.h"
15 #include "components/drive/file_system/operation_delegate.h"
16 #include "components/drive/file_system_core_util.h"
17 #include "components/drive/job_scheduler.h"
18 #include "components/drive/resource_entry_conversion.h"
19 #include "components/drive/resource_metadata.h"
20 #include "google_apis/drive/drive_api_parser.h"
22 namespace drive {
23 namespace file_system {
25 struct CopyOperation::CopyParams {
26 base::FilePath src_file_path;
27 base::FilePath dest_file_path;
28 bool preserve_last_modified;
29 FileOperationCallback callback;
30 ResourceEntry src_entry;
31 ResourceEntry parent_entry;
34 // Enum for categorizing where a gdoc represented by a JSON file exists.
35 enum JsonGdocLocationType {
36 NOT_IN_METADATA,
37 IS_ORPHAN,
38 HAS_PARENT,
41 struct CopyOperation::TransferJsonGdocParams {
42 TransferJsonGdocParams(const FileOperationCallback& callback,
43 const std::string& resource_id,
44 const ResourceEntry& parent_entry,
45 const std::string& new_title)
46 : callback(callback),
47 resource_id(resource_id),
48 parent_resource_id(parent_entry.resource_id()),
49 parent_local_id(parent_entry.local_id()),
50 new_title(new_title),
51 location_type(NOT_IN_METADATA) {
53 // Parameters supplied or calculated from operation arguments.
54 const FileOperationCallback callback;
55 const std::string resource_id;
56 const std::string parent_resource_id;
57 const std::string parent_local_id;
58 const std::string new_title;
60 // Values computed during operation.
61 JsonGdocLocationType location_type; // types where the gdoc file is located.
62 std::string local_id; // the local_id of the file (if exists in metadata.)
63 base::FilePath changed_path;
66 namespace {
68 FileError TryToCopyLocally(internal::ResourceMetadata* metadata,
69 internal::FileCache* cache,
70 CopyOperation::CopyParams* params,
71 std::vector<std::string>* updated_local_ids,
72 bool* directory_changed,
73 bool* should_copy_on_server) {
74 FileError error = metadata->GetResourceEntryByPath(params->src_file_path,
75 &params->src_entry);
76 if (error != FILE_ERROR_OK)
77 return error;
79 error = metadata->GetResourceEntryByPath(params->dest_file_path.DirName(),
80 &params->parent_entry);
81 if (error != FILE_ERROR_OK)
82 return error;
84 if (!params->parent_entry.file_info().is_directory())
85 return FILE_ERROR_NOT_A_DIRECTORY;
87 // Drive File System doesn't support recursive copy.
88 if (params->src_entry.file_info().is_directory())
89 return FILE_ERROR_NOT_A_FILE;
91 // Check destination.
92 ResourceEntry dest_entry;
93 error = metadata->GetResourceEntryByPath(params->dest_file_path, &dest_entry);
94 switch (error) {
95 case FILE_ERROR_OK:
96 // File API spec says it is an error to try to "copy a file to a path
97 // occupied by a directory".
98 if (dest_entry.file_info().is_directory())
99 return FILE_ERROR_INVALID_OPERATION;
101 // Move the existing entry to the trash.
102 dest_entry.set_parent_local_id(util::kDriveTrashDirLocalId);
103 error = metadata->RefreshEntry(dest_entry);
104 if (error != FILE_ERROR_OK)
105 return error;
106 updated_local_ids->push_back(dest_entry.local_id());
107 *directory_changed = true;
108 break;
109 case FILE_ERROR_NOT_FOUND:
110 break;
111 default:
112 return error;
115 // If the cache file is not present and the entry exists on the server,
116 // server side copy should be used.
117 if (!params->src_entry.file_specific_info().cache_state().is_present() &&
118 !params->src_entry.resource_id().empty()) {
119 *should_copy_on_server = true;
120 return FILE_ERROR_OK;
123 // Copy locally.
124 ResourceEntry entry;
125 const int64 now = base::Time::Now().ToInternalValue();
126 entry.set_title(params->dest_file_path.BaseName().AsUTF8Unsafe());
127 entry.set_parent_local_id(params->parent_entry.local_id());
128 entry.mutable_file_specific_info()->set_content_mime_type(
129 params->src_entry.file_specific_info().content_mime_type());
130 entry.set_metadata_edit_state(ResourceEntry::DIRTY);
131 entry.set_modification_date(base::Time::Now().ToInternalValue());
132 entry.mutable_file_info()->set_last_modified(
133 params->preserve_last_modified ?
134 params->src_entry.file_info().last_modified() : now);
135 entry.mutable_file_info()->set_last_accessed(now);
137 std::string local_id;
138 error = metadata->AddEntry(entry, &local_id);
139 if (error != FILE_ERROR_OK)
140 return error;
141 updated_local_ids->push_back(local_id);
142 *directory_changed = true;
144 if (!params->src_entry.file_specific_info().cache_state().is_present()) {
145 DCHECK(params->src_entry.resource_id().empty());
146 // Locally created empty file may have no cache file.
147 return FILE_ERROR_OK;
150 base::FilePath cache_file_path;
151 error = cache->GetFile(params->src_entry.local_id(), &cache_file_path);
152 if (error != FILE_ERROR_OK)
153 return error;
155 return cache->Store(local_id, std::string(), cache_file_path,
156 internal::FileCache::FILE_OPERATION_COPY);
159 // Stores the entry returned from the server and returns its path.
160 FileError UpdateLocalStateForServerSideOperation(
161 internal::ResourceMetadata* metadata,
162 scoped_ptr<google_apis::FileResource> file_resource,
163 ResourceEntry* entry,
164 base::FilePath* file_path) {
165 DCHECK(file_resource);
167 std::string parent_resource_id;
168 if (!ConvertFileResourceToResourceEntry(
169 *file_resource, entry, &parent_resource_id) ||
170 parent_resource_id.empty())
171 return FILE_ERROR_NOT_A_FILE;
173 std::string parent_local_id;
174 FileError error = metadata->GetIdByResourceId(parent_resource_id,
175 &parent_local_id);
176 if (error != FILE_ERROR_OK)
177 return error;
178 entry->set_parent_local_id(parent_local_id);
180 std::string local_id;
181 error = metadata->AddEntry(*entry, &local_id);
182 // Depending on timing, the metadata may have inserted via change list
183 // already. So, FILE_ERROR_EXISTS is not an error.
184 if (error == FILE_ERROR_EXISTS)
185 error = metadata->GetIdByResourceId(entry->resource_id(), &local_id);
187 if (error != FILE_ERROR_OK)
188 return error;
190 return metadata->GetFilePath(local_id, file_path);
193 // Stores the file at |local_file_path| to the cache as a content of entry at
194 // |remote_dest_path|, and marks it dirty.
195 FileError UpdateLocalStateForScheduleTransfer(
196 internal::ResourceMetadata* metadata,
197 internal::FileCache* cache,
198 const base::FilePath& local_src_path,
199 const base::FilePath& remote_dest_path,
200 ResourceEntry* entry,
201 std::string* local_id) {
202 FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
203 if (error != FILE_ERROR_OK)
204 return error;
206 error = metadata->GetResourceEntryById(*local_id, entry);
207 if (error != FILE_ERROR_OK)
208 return error;
210 return cache->Store(*local_id, std::string(), local_src_path,
211 internal::FileCache::FILE_OPERATION_COPY);
214 // Gets the file size of the |local_path|, and the ResourceEntry for the parent
215 // of |remote_path| to prepare the necessary information for transfer.
216 FileError PrepareTransferFileFromLocalToRemote(
217 internal::ResourceMetadata* metadata,
218 const base::FilePath& local_src_path,
219 const base::FilePath& remote_dest_path,
220 std::string* gdoc_resource_id,
221 ResourceEntry* parent_entry) {
222 FileError error = metadata->GetResourceEntryByPath(
223 remote_dest_path.DirName(), parent_entry);
224 if (error != FILE_ERROR_OK)
225 return error;
227 // The destination's parent must be a directory.
228 if (!parent_entry->file_info().is_directory())
229 return FILE_ERROR_NOT_A_DIRECTORY;
231 // Try to parse GDoc File and extract the resource id, if necessary.
232 // Failing isn't problem. It'd be handled as a regular file, then.
233 if (util::HasHostedDocumentExtension(local_src_path))
234 *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
235 return FILE_ERROR_OK;
238 // Performs local work before server-side work for transferring JSON-represented
239 // gdoc files.
240 FileError LocalWorkForTransferJsonGdocFile(
241 internal::ResourceMetadata* metadata,
242 CopyOperation::TransferJsonGdocParams* params) {
243 std::string local_id;
244 FileError error = metadata->GetIdByResourceId(params->resource_id, &local_id);
245 if (error != FILE_ERROR_OK) {
246 params->location_type = NOT_IN_METADATA;
247 return error == FILE_ERROR_NOT_FOUND ? FILE_ERROR_OK : error;
250 ResourceEntry entry;
251 error = metadata->GetResourceEntryById(local_id, &entry);
252 if (error != FILE_ERROR_OK)
253 return error;
254 params->local_id = entry.local_id();
256 if (entry.parent_local_id() == util::kDriveOtherDirLocalId) {
257 params->location_type = IS_ORPHAN;
258 entry.set_title(params->new_title);
259 entry.set_parent_local_id(params->parent_local_id);
260 entry.set_metadata_edit_state(ResourceEntry::DIRTY);
261 entry.set_modification_date(base::Time::Now().ToInternalValue());
262 error = metadata->RefreshEntry(entry);
263 if (error != FILE_ERROR_OK)
264 return error;
265 return metadata->GetFilePath(local_id, &params->changed_path);
268 params->location_type = HAS_PARENT;
269 return FILE_ERROR_OK;
272 } // namespace
274 CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
275 OperationDelegate* delegate,
276 JobScheduler* scheduler,
277 internal::ResourceMetadata* metadata,
278 internal::FileCache* cache)
279 : blocking_task_runner_(blocking_task_runner),
280 delegate_(delegate),
281 scheduler_(scheduler),
282 metadata_(metadata),
283 cache_(cache),
284 create_file_operation_(new CreateFileOperation(blocking_task_runner,
285 delegate,
286 metadata)),
287 weak_ptr_factory_(this) {
290 CopyOperation::~CopyOperation() {
291 DCHECK(thread_checker_.CalledOnValidThread());
294 void CopyOperation::Copy(const base::FilePath& src_file_path,
295 const base::FilePath& dest_file_path,
296 bool preserve_last_modified,
297 const FileOperationCallback& callback) {
298 DCHECK(thread_checker_.CalledOnValidThread());
299 DCHECK(!callback.is_null());
301 CopyParams* params = new CopyParams;
302 params->src_file_path = src_file_path;
303 params->dest_file_path = dest_file_path;
304 params->preserve_last_modified = preserve_last_modified;
305 params->callback = callback;
307 std::vector<std::string>* updated_local_ids = new std::vector<std::string>;
308 bool* directory_changed = new bool(false);
309 bool* should_copy_on_server = new bool(false);
310 base::PostTaskAndReplyWithResult(
311 blocking_task_runner_.get(),
312 FROM_HERE,
313 base::Bind(&TryToCopyLocally, metadata_, cache_, params,
314 updated_local_ids, directory_changed, should_copy_on_server),
315 base::Bind(&CopyOperation::CopyAfterTryToCopyLocally,
316 weak_ptr_factory_.GetWeakPtr(), base::Owned(params),
317 base::Owned(updated_local_ids), base::Owned(directory_changed),
318 base::Owned(should_copy_on_server)));
321 void CopyOperation::CopyAfterTryToCopyLocally(
322 const CopyParams* params,
323 const std::vector<std::string>* updated_local_ids,
324 const bool* directory_changed,
325 const bool* should_copy_on_server,
326 FileError error) {
327 DCHECK(thread_checker_.CalledOnValidThread());
328 DCHECK(!params->callback.is_null());
330 for (const auto& id : *updated_local_ids) {
331 // Syncing for copy should be done in background, so pass the BACKGROUND
332 // context. See: crbug.com/420278.
333 delegate_->OnEntryUpdatedByOperation(ClientContext(BACKGROUND), id);
336 if (*directory_changed) {
337 FileChange changed_file;
338 DCHECK(!params->src_entry.file_info().is_directory());
339 changed_file.Update(params->dest_file_path, FileChange::FILE_TYPE_FILE,
340 FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
341 delegate_->OnFileChangedByOperation(changed_file);
344 if (error != FILE_ERROR_OK || !*should_copy_on_server) {
345 params->callback.Run(error);
346 return;
349 if (params->parent_entry.resource_id().empty()) {
350 // Parent entry may be being synced.
351 const bool waiting = delegate_->WaitForSyncComplete(
352 params->parent_entry.local_id(),
353 base::Bind(&CopyOperation::CopyAfterParentSync,
354 weak_ptr_factory_.GetWeakPtr(), *params));
355 if (!waiting)
356 params->callback.Run(FILE_ERROR_NOT_FOUND);
357 } else {
358 CopyAfterGetParentResourceId(*params, &params->parent_entry, FILE_ERROR_OK);
362 void CopyOperation::CopyAfterParentSync(const CopyParams& params,
363 FileError error) {
364 DCHECK(thread_checker_.CalledOnValidThread());
365 DCHECK(!params.callback.is_null());
367 if (error != FILE_ERROR_OK) {
368 params.callback.Run(error);
369 return;
372 ResourceEntry* parent = new ResourceEntry;
373 base::PostTaskAndReplyWithResult(
374 blocking_task_runner_.get(),
375 FROM_HERE,
376 base::Bind(&internal::ResourceMetadata::GetResourceEntryById,
377 base::Unretained(metadata_),
378 params.parent_entry.local_id(),
379 parent),
380 base::Bind(&CopyOperation::CopyAfterGetParentResourceId,
381 weak_ptr_factory_.GetWeakPtr(),
382 params,
383 base::Owned(parent)));
386 void CopyOperation::CopyAfterGetParentResourceId(const CopyParams& params,
387 const ResourceEntry* parent,
388 FileError error) {
389 DCHECK(thread_checker_.CalledOnValidThread());
390 DCHECK(!params.callback.is_null());
392 if (error != FILE_ERROR_OK) {
393 params.callback.Run(error);
394 return;
397 base::FilePath new_title = params.dest_file_path.BaseName();
398 if (params.src_entry.file_specific_info().is_hosted_document()) {
399 // Drop the document extension, which should not be in the title.
400 // TODO(yoshiki): Remove this code with crbug.com/223304.
401 new_title = new_title.RemoveExtension();
404 base::Time last_modified =
405 params.preserve_last_modified ?
406 base::Time::FromInternalValue(
407 params.src_entry.file_info().last_modified()) : base::Time();
409 CopyResourceOnServer(
410 params.src_entry.resource_id(), parent->resource_id(),
411 new_title.AsUTF8Unsafe(), last_modified, params.callback);
414 void CopyOperation::TransferFileFromLocalToRemote(
415 const base::FilePath& local_src_path,
416 const base::FilePath& remote_dest_path,
417 const FileOperationCallback& callback) {
418 DCHECK(thread_checker_.CalledOnValidThread());
419 DCHECK(!callback.is_null());
421 std::string* gdoc_resource_id = new std::string;
422 ResourceEntry* parent_entry = new ResourceEntry;
423 base::PostTaskAndReplyWithResult(
424 blocking_task_runner_.get(),
425 FROM_HERE,
426 base::Bind(
427 &PrepareTransferFileFromLocalToRemote,
428 metadata_, local_src_path, remote_dest_path,
429 gdoc_resource_id, parent_entry),
430 base::Bind(
431 &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
432 weak_ptr_factory_.GetWeakPtr(),
433 local_src_path, remote_dest_path, callback,
434 base::Owned(gdoc_resource_id), base::Owned(parent_entry)));
437 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
438 const base::FilePath& local_src_path,
439 const base::FilePath& remote_dest_path,
440 const FileOperationCallback& callback,
441 std::string* gdoc_resource_id,
442 ResourceEntry* parent_entry,
443 FileError error) {
444 DCHECK(thread_checker_.CalledOnValidThread());
445 DCHECK(!callback.is_null());
447 if (error != FILE_ERROR_OK) {
448 callback.Run(error);
449 return;
452 // For regular files, schedule the transfer.
453 if (gdoc_resource_id->empty()) {
454 ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
455 return;
458 // GDoc file may contain a resource ID in the old format.
459 const std::string canonicalized_resource_id =
460 util::CanonicalizeResourceId(*gdoc_resource_id);
462 // Drop the document extension, which should not be in the title.
463 // TODO(yoshiki): Remove this code with crbug.com/223304.
464 const std::string new_title =
465 remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe();
467 // This is uploading a JSON file representing a hosted document.
468 TransferJsonGdocParams* params = new TransferJsonGdocParams(
469 callback, canonicalized_resource_id, *parent_entry, new_title);
470 base::PostTaskAndReplyWithResult(
471 blocking_task_runner_.get(),
472 FROM_HERE,
473 base::Bind(&LocalWorkForTransferJsonGdocFile, metadata_, params),
474 base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork,
475 weak_ptr_factory_.GetWeakPtr(), base::Owned(params)));
478 void CopyOperation::TransferJsonGdocFileAfterLocalWork(
479 TransferJsonGdocParams* params,
480 FileError error) {
481 DCHECK(thread_checker_.CalledOnValidThread());
483 if (error != FILE_ERROR_OK) {
484 params->callback.Run(error);
485 return;
488 switch (params->location_type) {
489 // When |resource_id| is found in the local metadata and it has a specific
490 // parent folder, we assume the user's intention is to copy the document and
491 // thus perform the server-side copy operation.
492 case HAS_PARENT:
493 CopyResourceOnServer(params->resource_id,
494 params->parent_resource_id,
495 params->new_title,
496 base::Time(),
497 params->callback);
498 break;
499 // When |resource_id| has no parent, we just set the new destination folder
500 // as the parent, for sharing the document between the original source.
501 // This reparenting is already done in LocalWorkForTransferJsonGdocFile().
502 case IS_ORPHAN: {
503 DCHECK(!params->changed_path.empty());
504 // Syncing for copy should be done in background, so pass the BACKGROUND
505 // context. See: crbug.com/420278.
506 delegate_->OnEntryUpdatedByOperation(ClientContext(BACKGROUND),
507 params->local_id);
509 FileChange changed_file;
510 changed_file.Update(
511 params->changed_path,
512 FileChange::FILE_TYPE_FILE, // This must be a hosted document.
513 FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
514 delegate_->OnFileChangedByOperation(changed_file);
515 params->callback.Run(error);
516 break;
518 // When the |resource_id| is not in the local metadata, assume it to be a
519 // document just now shared on the server but not synced locally.
520 // Same as the IS_ORPHAN case, we want to deal the case by setting parent,
521 // but this time we need to resort to server side operation.
522 case NOT_IN_METADATA:
523 scheduler_->UpdateResource(
524 params->resource_id, params->parent_resource_id, params->new_title,
525 base::Time(), base::Time(), google_apis::drive::Properties(),
526 ClientContext(USER_INITIATED),
527 base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
528 weak_ptr_factory_.GetWeakPtr(), params->callback));
529 break;
533 void 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(thread_checker_.CalledOnValidThread());
540 DCHECK(!callback.is_null());
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));
549 void CopyOperation::UpdateAfterServerSideOperation(
550 const FileOperationCallback& callback,
551 google_apis::DriveApiErrorCode status,
552 scoped_ptr<google_apis::FileResource> entry) {
553 DCHECK(thread_checker_.CalledOnValidThread());
554 DCHECK(!callback.is_null());
556 FileError error = GDataToFileError(status);
557 if (error != FILE_ERROR_OK) {
558 callback.Run(error);
559 return;
562 ResourceEntry* resource_entry = new ResourceEntry;
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)));
582 void CopyOperation::UpdateAfterLocalStateUpdate(
583 const FileOperationCallback& callback,
584 base::FilePath* file_path,
585 const ResourceEntry* entry,
586 FileError error) {
587 DCHECK(thread_checker_.CalledOnValidThread());
588 DCHECK(!callback.is_null());
590 if (error == FILE_ERROR_OK) {
591 FileChange changed_file;
592 changed_file.Update(*file_path, *entry,
593 FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
594 delegate_->OnFileChangedByOperation(changed_file);
596 callback.Run(error);
599 void CopyOperation::ScheduleTransferRegularFile(
600 const base::FilePath& local_src_path,
601 const base::FilePath& remote_dest_path,
602 const FileOperationCallback& callback) {
603 DCHECK(thread_checker_.CalledOnValidThread());
604 DCHECK(!callback.is_null());
606 create_file_operation_->CreateFile(
607 remote_dest_path,
608 false, // Not exclusive (OK even if a file already exists).
609 std::string(), // no specific mime type; CreateFile should guess it.
610 base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
611 weak_ptr_factory_.GetWeakPtr(),
612 local_src_path, remote_dest_path, callback));
615 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
616 const base::FilePath& local_src_path,
617 const base::FilePath& remote_dest_path,
618 const FileOperationCallback& callback,
619 FileError error) {
620 DCHECK(thread_checker_.CalledOnValidThread());
621 DCHECK(!callback.is_null());
623 if (error != FILE_ERROR_OK) {
624 callback.Run(error);
625 return;
628 std::string* local_id = new std::string;
629 ResourceEntry* entry = new ResourceEntry;
630 base::PostTaskAndReplyWithResult(
631 blocking_task_runner_.get(),
632 FROM_HERE,
633 base::Bind(&UpdateLocalStateForScheduleTransfer,
634 metadata_,
635 cache_,
636 local_src_path,
637 remote_dest_path,
638 entry,
639 local_id),
640 base::Bind(
641 &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
642 weak_ptr_factory_.GetWeakPtr(),
643 callback,
644 remote_dest_path,
645 base::Owned(entry),
646 base::Owned(local_id)));
649 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
650 const FileOperationCallback& callback,
651 const base::FilePath& remote_dest_path,
652 const ResourceEntry* entry,
653 std::string* local_id,
654 FileError error) {
655 DCHECK(thread_checker_.CalledOnValidThread());
656 DCHECK(!callback.is_null());
658 if (error == FILE_ERROR_OK) {
659 FileChange changed_file;
660 changed_file.Update(remote_dest_path, *entry,
661 FileChange::CHANGE_TYPE_ADD_OR_UPDATE);
662 delegate_->OnFileChangedByOperation(changed_file);
663 // Syncing for copy should be done in background, so pass the BACKGROUND
664 // context. See: crbug.com/420278.
665 delegate_->OnEntryUpdatedByOperation(ClientContext(BACKGROUND), *local_id);
667 callback.Run(error);
670 } // namespace file_system
671 } // namespace drive