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 "chrome/browser/chromeos/drive/file_system/copy_operation.h"
9 #include "base/file_util.h"
10 #include "base/task_runner_util.h"
11 #include "chrome/browser/chromeos/drive/drive.pb.h"
12 #include "chrome/browser/chromeos/drive/file_cache.h"
13 #include "chrome/browser/chromeos/drive/file_system/create_file_operation.h"
14 #include "chrome/browser/chromeos/drive/file_system/operation_observer.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"
23 using content::BrowserThread;
26 namespace file_system {
28 struct 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;
37 // Enum for categorizing where a gdoc represented by a JSON file exists.
38 enum JsonGdocLocationType {
44 struct CopyOperation::TransferJsonGdocParams {
45 TransferJsonGdocParams(const FileOperationCallback& callback,
46 const std::string& resource_id,
47 const ResourceEntry& parent_entry,
48 const std::string& new_title)
50 resource_id(resource_id),
51 parent_resource_id(parent_entry.resource_id()),
52 parent_local_id(parent_entry.local_id()),
54 location_type(NOT_IN_METADATA) {
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;
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;
71 FileError 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,
79 if (error != FILE_ERROR_OK)
82 error = metadata->GetResourceEntryByPath(params->dest_file_path.DirName(),
83 ¶ms->parent_entry);
84 if (error != FILE_ERROR_OK)
87 if (!params->parent_entry.file_info().is_directory())
88 return FILE_ERROR_NOT_A_DIRECTORY;
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;
95 ResourceEntry dest_entry;
96 error = metadata->GetResourceEntryByPath(params->dest_file_path, &dest_entry);
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;
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)
109 updated_local_ids->push_back(dest_entry.local_id());
110 *directory_changed = true;
112 case FILE_ERROR_NOT_FOUND:
118 // If the cache file is not present and the entry exists on the server,
119 // server side copy should be used.
120 FileCacheEntry cache_entry;
121 cache->GetCacheEntry(params->src_entry.local_id(), &cache_entry);
122 if (!cache_entry.is_present() && !params->src_entry.resource_id().empty()) {
123 *should_copy_on_server = true;
124 return FILE_ERROR_OK;
129 const int64 now = base::Time::Now().ToInternalValue();
130 entry.set_title(params->dest_file_path.BaseName().AsUTF8Unsafe());
131 entry.set_parent_local_id(params->parent_entry.local_id());
132 entry.mutable_file_specific_info()->set_content_mime_type(
133 params->src_entry.file_specific_info().content_mime_type());
134 entry.set_metadata_edit_state(ResourceEntry::DIRTY);
135 entry.set_modification_date(base::Time::Now().ToInternalValue());
136 entry.mutable_file_info()->set_last_modified(
137 params->preserve_last_modified ?
138 params->src_entry.file_info().last_modified() : now);
139 entry.mutable_file_info()->set_last_accessed(now);
141 std::string local_id;
142 error = metadata->AddEntry(entry, &local_id);
143 if (error != FILE_ERROR_OK)
145 updated_local_ids->push_back(local_id);
146 *directory_changed = true;
148 if (!cache_entry.is_present()) {
149 DCHECK(params->src_entry.resource_id().empty());
150 // Locally created empty file may have no cache file.
151 return FILE_ERROR_OK;
154 base::FilePath cache_file_path;
155 error = cache->GetFile(params->src_entry.local_id(), &cache_file_path);
156 if (error != FILE_ERROR_OK)
159 return cache->Store(local_id, std::string(), cache_file_path,
160 internal::FileCache::FILE_OPERATION_COPY);
163 // Stores the entry returned from the server and returns its path.
164 FileError UpdateLocalStateForServerSideOperation(
165 internal::ResourceMetadata* metadata,
166 scoped_ptr<google_apis::ResourceEntry> resource_entry,
167 base::FilePath* file_path) {
168 DCHECK(resource_entry);
171 std::string parent_resource_id;
172 if (!ConvertToResourceEntry(*resource_entry, &entry, &parent_resource_id) ||
173 parent_resource_id.empty())
174 return FILE_ERROR_NOT_A_FILE;
176 std::string parent_local_id;
177 FileError error = metadata->GetIdByResourceId(parent_resource_id,
179 if (error != FILE_ERROR_OK)
181 entry.set_parent_local_id(parent_local_id);
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);
190 if (error == FILE_ERROR_OK)
191 *file_path = metadata->GetFilePath(local_id);
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.
198 FileError UpdateLocalStateForScheduleTransfer(
199 internal::ResourceMetadata* metadata,
200 internal::FileCache* cache,
201 const base::FilePath& local_src_path,
202 const base::FilePath& remote_dest_path,
203 std::string* local_id) {
204 FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
205 if (error != FILE_ERROR_OK)
209 error = metadata->GetResourceEntryById(*local_id, &entry);
210 if (error != FILE_ERROR_OK)
213 return cache->Store(*local_id, std::string(), local_src_path,
214 internal::FileCache::FILE_OPERATION_COPY);
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.
219 FileError 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)
230 // The destination's parent must be a directory.
231 if (!parent_entry->file_info().is_directory())
232 return FILE_ERROR_NOT_A_DIRECTORY;
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::HasGDocFileExtension(local_src_path))
237 *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
238 return FILE_ERROR_OK;
241 // Performs local work before server-side work for transferring JSON-represented
243 FileError 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;
254 error = metadata->GetResourceEntryById(local_id, &entry);
255 if (error != FILE_ERROR_OK)
257 params->local_id = entry.local_id();
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 params->changed_path = metadata->GetFilePath(local_id);
271 params->location_type = HAS_PARENT;
272 return FILE_ERROR_OK;
277 CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
278 OperationObserver* observer,
279 JobScheduler* scheduler,
280 internal::ResourceMetadata* metadata,
281 internal::FileCache* cache,
282 const ResourceIdCanonicalizer& id_canonicalizer)
283 : blocking_task_runner_(blocking_task_runner),
285 scheduler_(scheduler),
288 id_canonicalizer_(id_canonicalizer),
289 create_file_operation_(new CreateFileOperation(blocking_task_runner,
292 weak_ptr_factory_(this) {
293 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
296 CopyOperation::~CopyOperation() {
297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
300 void CopyOperation::Copy(const base::FilePath& src_file_path,
301 const base::FilePath& dest_file_path,
302 bool preserve_last_modified,
303 const FileOperationCallback& callback) {
304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
305 DCHECK(!callback.is_null());
307 CopyParams* params = new CopyParams;
308 params->src_file_path = src_file_path;
309 params->dest_file_path = dest_file_path;
310 params->preserve_last_modified = preserve_last_modified;
311 params->callback = callback;
313 std::vector<std::string>* updated_local_ids = new std::vector<std::string>;
314 bool* directory_changed = new bool(false);
315 bool* should_copy_on_server = new bool(false);
316 base::PostTaskAndReplyWithResult(
317 blocking_task_runner_.get(),
319 base::Bind(&TryToCopyLocally, metadata_, cache_, params,
320 updated_local_ids, directory_changed, should_copy_on_server),
321 base::Bind(&CopyOperation::CopyAfterTryToCopyLocally,
322 weak_ptr_factory_.GetWeakPtr(), base::Owned(params),
323 base::Owned(updated_local_ids), base::Owned(directory_changed),
324 base::Owned(should_copy_on_server)));
327 void CopyOperation::CopyAfterTryToCopyLocally(
328 const CopyParams* params,
329 const std::vector<std::string>* updated_local_ids,
330 const bool* directory_changed,
331 const bool* should_copy_on_server,
333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
334 DCHECK(!params->callback.is_null());
336 for (size_t i = 0; i < updated_local_ids->size(); ++i)
337 observer_->OnEntryUpdatedByOperation((*updated_local_ids)[i]);
339 if (*directory_changed)
340 observer_->OnDirectoryChangedByOperation(params->dest_file_path.DirName());
342 if (error != FILE_ERROR_OK || !*should_copy_on_server) {
343 params->callback.Run(error);
347 base::FilePath new_title = params->dest_file_path.BaseName();
348 if (params->src_entry.file_specific_info().is_hosted_document()) {
349 // Drop the document extension, which should not be in the title.
350 // TODO(yoshiki): Remove this code with crbug.com/223304.
351 new_title = new_title.RemoveExtension();
354 base::Time last_modified =
355 params->preserve_last_modified ?
356 base::Time::FromInternalValue(
357 params->src_entry.file_info().last_modified()) : base::Time();
359 CopyResourceOnServer(
360 params->src_entry.resource_id(), params->parent_entry.resource_id(),
361 new_title.AsUTF8Unsafe(), last_modified, params->callback);
364 void CopyOperation::TransferFileFromLocalToRemote(
365 const base::FilePath& local_src_path,
366 const base::FilePath& remote_dest_path,
367 const FileOperationCallback& callback) {
368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
369 DCHECK(!callback.is_null());
371 std::string* gdoc_resource_id = new std::string;
372 ResourceEntry* parent_entry = new ResourceEntry;
373 base::PostTaskAndReplyWithResult(
374 blocking_task_runner_.get(),
377 &PrepareTransferFileFromLocalToRemote,
378 metadata_, local_src_path, remote_dest_path,
379 gdoc_resource_id, parent_entry),
381 &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
382 weak_ptr_factory_.GetWeakPtr(),
383 local_src_path, remote_dest_path, callback,
384 base::Owned(gdoc_resource_id), base::Owned(parent_entry)));
387 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
388 const base::FilePath& local_src_path,
389 const base::FilePath& remote_dest_path,
390 const FileOperationCallback& callback,
391 std::string* gdoc_resource_id,
392 ResourceEntry* parent_entry,
394 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
395 DCHECK(!callback.is_null());
397 if (error != FILE_ERROR_OK) {
402 // For regular files, schedule the transfer.
403 if (gdoc_resource_id->empty()) {
404 ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
408 // GDoc file may contain a resource ID in the old format.
409 const std::string canonicalized_resource_id =
410 id_canonicalizer_.Run(*gdoc_resource_id);
412 // Drop the document extension, which should not be in the title.
413 // TODO(yoshiki): Remove this code with crbug.com/223304.
414 const std::string new_title =
415 remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe();
417 // This is uploading a JSON file representing a hosted document.
418 TransferJsonGdocParams* params = new TransferJsonGdocParams(
419 callback, canonicalized_resource_id, *parent_entry, new_title);
420 base::PostTaskAndReplyWithResult(
421 blocking_task_runner_.get(),
423 base::Bind(&LocalWorkForTransferJsonGdocFile, metadata_, params),
424 base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork,
425 weak_ptr_factory_.GetWeakPtr(), base::Owned(params)));
428 void CopyOperation::TransferJsonGdocFileAfterLocalWork(
429 TransferJsonGdocParams* params,
431 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
433 if (error != FILE_ERROR_OK) {
434 params->callback.Run(error);
438 switch (params->location_type) {
439 // When |resource_id| is found in the local metadata and it has a specific
440 // parent folder, we assume the user's intention is to copy the document and
441 // thus perform the server-side copy operation.
443 CopyResourceOnServer(params->resource_id,
444 params->parent_resource_id,
449 // When |resource_id| has no parent, we just set the new destination folder
450 // as the parent, for sharing the document between the original source.
451 // This reparenting is already done in LocalWorkForTransferJsonGdocFile().
453 DCHECK(!params->changed_path.empty());
454 observer_->OnEntryUpdatedByOperation(params->local_id);
455 observer_->OnDirectoryChangedByOperation(params->changed_path.DirName());
456 params->callback.Run(error);
458 // When the |resource_id| is not in the local metadata, assume it to be a
459 // document just now shared on the server but not synced locally.
460 // Same as the IS_ORPHAN case, we want to deal the case by setting parent,
461 // but this time we need to resort to server side operation.
462 case NOT_IN_METADATA:
463 scheduler_->UpdateResource(
465 params->parent_resource_id,
469 ClientContext(USER_INITIATED),
470 base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
471 weak_ptr_factory_.GetWeakPtr(),
477 void CopyOperation::CopyResourceOnServer(
478 const std::string& resource_id,
479 const std::string& parent_resource_id,
480 const std::string& new_title,
481 const base::Time& last_modified,
482 const FileOperationCallback& callback) {
483 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
484 DCHECK(!callback.is_null());
486 scheduler_->CopyResource(
487 resource_id, parent_resource_id, new_title, last_modified,
488 base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
489 weak_ptr_factory_.GetWeakPtr(),
493 void CopyOperation::UpdateAfterServerSideOperation(
494 const FileOperationCallback& callback,
495 google_apis::GDataErrorCode status,
496 scoped_ptr<google_apis::ResourceEntry> resource_entry) {
497 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
498 DCHECK(!callback.is_null());
500 FileError error = GDataToFileError(status);
501 if (error != FILE_ERROR_OK) {
506 // The copy on the server side is completed successfully. Update the local
508 base::FilePath* file_path = new base::FilePath;
509 base::PostTaskAndReplyWithResult(
510 blocking_task_runner_.get(),
512 base::Bind(&UpdateLocalStateForServerSideOperation,
513 metadata_, base::Passed(&resource_entry), file_path),
514 base::Bind(&CopyOperation::UpdateAfterLocalStateUpdate,
515 weak_ptr_factory_.GetWeakPtr(),
516 callback, base::Owned(file_path)));
519 void CopyOperation::UpdateAfterLocalStateUpdate(
520 const FileOperationCallback& callback,
521 base::FilePath* file_path,
523 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
524 DCHECK(!callback.is_null());
526 if (error == FILE_ERROR_OK)
527 observer_->OnDirectoryChangedByOperation(file_path->DirName());
531 void CopyOperation::ScheduleTransferRegularFile(
532 const base::FilePath& local_src_path,
533 const base::FilePath& remote_dest_path,
534 const FileOperationCallback& callback) {
535 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
536 DCHECK(!callback.is_null());
538 create_file_operation_->CreateFile(
540 false, // Not exclusive (OK even if a file already exists).
541 std::string(), // no specific mime type; CreateFile should guess it.
542 base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
543 weak_ptr_factory_.GetWeakPtr(),
544 local_src_path, remote_dest_path, callback));
547 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
548 const base::FilePath& local_src_path,
549 const base::FilePath& remote_dest_path,
550 const FileOperationCallback& callback,
552 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
553 DCHECK(!callback.is_null());
555 if (error != FILE_ERROR_OK) {
560 std::string* local_id = new std::string;
561 base::PostTaskAndReplyWithResult(
562 blocking_task_runner_.get(),
565 &UpdateLocalStateForScheduleTransfer,
566 metadata_, cache_, local_src_path, remote_dest_path, local_id),
568 &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
569 weak_ptr_factory_.GetWeakPtr(), callback, remote_dest_path,
570 base::Owned(local_id)));
573 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
574 const FileOperationCallback& callback,
575 const base::FilePath& remote_dest_path,
576 std::string* local_id,
578 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
579 DCHECK(!callback.is_null());
581 if (error == FILE_ERROR_OK) {
582 observer_->OnDirectoryChangedByOperation(remote_dest_path.DirName());
583 observer_->OnEntryUpdatedByOperation(*local_id);
588 } // namespace file_system