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;
39 FileError TryToCopyLocally(internal::ResourceMetadata* metadata,
40 internal::FileCache* cache,
41 CopyOperation::CopyParams* params,
42 std::vector<std::string>* updated_local_ids,
43 bool* directory_changed,
44 bool* should_copy_on_server) {
45 FileError error = metadata->GetResourceEntryByPath(params->src_file_path,
47 if (error != FILE_ERROR_OK)
50 error = metadata->GetResourceEntryByPath(params->dest_file_path.DirName(),
51 ¶ms->parent_entry);
52 if (error != FILE_ERROR_OK)
55 if (!params->parent_entry.file_info().is_directory())
56 return FILE_ERROR_NOT_A_DIRECTORY;
58 // Drive File System doesn't support recursive copy.
59 if (params->src_entry.file_info().is_directory())
60 return FILE_ERROR_NOT_A_FILE;
63 ResourceEntry dest_entry;
64 error = metadata->GetResourceEntryByPath(params->dest_file_path, &dest_entry);
67 // File API spec says it is an error to try to "copy a file to a path
68 // occupied by a directory".
69 if (dest_entry.file_info().is_directory())
70 return FILE_ERROR_INVALID_OPERATION;
72 // Move the existing entry to the trash.
73 dest_entry.set_parent_local_id(util::kDriveTrashDirLocalId);
74 error = metadata->RefreshEntry(dest_entry);
75 if (error != FILE_ERROR_OK)
77 updated_local_ids->push_back(dest_entry.local_id());
78 *directory_changed = true;
80 case FILE_ERROR_NOT_FOUND:
86 // If the cache file is not present and the entry exists on the server,
87 // server side copy should be used.
88 FileCacheEntry cache_entry;
89 cache->GetCacheEntry(params->src_entry.local_id(), &cache_entry);
90 if (!cache_entry.is_present() && !params->src_entry.resource_id().empty()) {
91 *should_copy_on_server = true;
97 const int64 now = base::Time::Now().ToInternalValue();
98 entry.set_title(params->dest_file_path.BaseName().AsUTF8Unsafe());
99 entry.set_parent_local_id(params->parent_entry.local_id());
100 entry.mutable_file_specific_info()->set_content_mime_type(
101 params->src_entry.file_specific_info().content_mime_type());
102 entry.set_metadata_edit_state(ResourceEntry::DIRTY);
103 entry.mutable_file_info()->set_last_modified(
104 params->preserve_last_modified ?
105 params->src_entry.file_info().last_modified() : now);
106 entry.mutable_file_info()->set_last_accessed(now);
108 std::string local_id;
109 error = metadata->AddEntry(entry, &local_id);
110 if (error != FILE_ERROR_OK)
112 updated_local_ids->push_back(local_id);
113 *directory_changed = true;
115 if (!cache_entry.is_present()) {
116 DCHECK(params->src_entry.resource_id().empty());
117 // Locally created empty file may have no cache file.
118 return FILE_ERROR_OK;
121 base::FilePath cache_file_path;
122 error = cache->GetFile(params->src_entry.local_id(), &cache_file_path);
123 if (error != FILE_ERROR_OK)
126 return cache->Store(local_id, std::string(), cache_file_path,
127 internal::FileCache::FILE_OPERATION_COPY);
130 // Stores the copied entry and returns its path.
131 FileError UpdateLocalStateForServerSideCopy(
132 internal::ResourceMetadata* metadata,
133 scoped_ptr<google_apis::ResourceEntry> resource_entry,
134 base::FilePath* file_path) {
135 DCHECK(resource_entry);
138 std::string parent_resource_id;
139 if (!ConvertToResourceEntry(*resource_entry, &entry, &parent_resource_id) ||
140 parent_resource_id.empty())
141 return FILE_ERROR_NOT_A_FILE;
143 std::string parent_local_id;
144 FileError error = metadata->GetIdByResourceId(parent_resource_id,
146 if (error != FILE_ERROR_OK)
148 entry.set_parent_local_id(parent_local_id);
150 std::string local_id;
151 error = metadata->AddEntry(entry, &local_id);
152 // Depending on timing, the metadata may have inserted via change list
153 // already. So, FILE_ERROR_EXISTS is not an error.
154 if (error == FILE_ERROR_EXISTS)
155 error = metadata->GetIdByResourceId(entry.resource_id(), &local_id);
157 if (error == FILE_ERROR_OK)
158 *file_path = metadata->GetFilePath(local_id);
163 // Stores the file at |local_file_path| to the cache as a content of entry at
164 // |remote_dest_path|, and marks it dirty.
165 FileError UpdateLocalStateForScheduleTransfer(
166 internal::ResourceMetadata* metadata,
167 internal::FileCache* cache,
168 const base::FilePath& local_src_path,
169 const base::FilePath& remote_dest_path,
170 std::string* local_id) {
171 FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
172 if (error != FILE_ERROR_OK)
176 error = metadata->GetResourceEntryById(*local_id, &entry);
177 if (error != FILE_ERROR_OK)
180 return cache->Store(*local_id, std::string(), local_src_path,
181 internal::FileCache::FILE_OPERATION_COPY);
184 // Gets the file size of the |local_path|, and the ResourceEntry for the parent
185 // of |remote_path| to prepare the necessary information for transfer.
186 FileError PrepareTransferFileFromLocalToRemote(
187 internal::ResourceMetadata* metadata,
188 const base::FilePath& local_src_path,
189 const base::FilePath& remote_dest_path,
190 std::string* gdoc_resource_id,
191 std::string* parent_resource_id) {
192 ResourceEntry parent_entry;
193 FileError error = metadata->GetResourceEntryByPath(
194 remote_dest_path.DirName(), &parent_entry);
195 if (error != FILE_ERROR_OK)
198 // The destination's parent must be a directory.
199 if (!parent_entry.file_info().is_directory())
200 return FILE_ERROR_NOT_A_DIRECTORY;
202 // Try to parse GDoc File and extract the resource id, if necessary.
203 // Failing isn't problem. It'd be handled as a regular file, then.
204 if (util::HasGDocFileExtension(local_src_path)) {
205 *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
206 *parent_resource_id = parent_entry.resource_id();
209 return FILE_ERROR_OK;
214 CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
215 OperationObserver* observer,
216 JobScheduler* scheduler,
217 internal::ResourceMetadata* metadata,
218 internal::FileCache* cache,
219 const ResourceIdCanonicalizer& id_canonicalizer)
220 : blocking_task_runner_(blocking_task_runner),
222 scheduler_(scheduler),
225 id_canonicalizer_(id_canonicalizer),
226 create_file_operation_(new CreateFileOperation(blocking_task_runner,
229 weak_ptr_factory_(this) {
230 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
233 CopyOperation::~CopyOperation() {
234 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
237 void CopyOperation::Copy(const base::FilePath& src_file_path,
238 const base::FilePath& dest_file_path,
239 bool preserve_last_modified,
240 const FileOperationCallback& callback) {
241 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
242 DCHECK(!callback.is_null());
244 CopyParams* params = new CopyParams;
245 params->src_file_path = src_file_path;
246 params->dest_file_path = dest_file_path;
247 params->preserve_last_modified = preserve_last_modified;
248 params->callback = callback;
250 std::vector<std::string>* updated_local_ids = new std::vector<std::string>;
251 bool* directory_changed = new bool(false);
252 bool* should_copy_on_server = new bool(false);
253 base::PostTaskAndReplyWithResult(
254 blocking_task_runner_.get(),
256 base::Bind(&TryToCopyLocally, metadata_, cache_, params,
257 updated_local_ids, directory_changed, should_copy_on_server),
258 base::Bind(&CopyOperation::CopyAfterTryToCopyLocally,
259 weak_ptr_factory_.GetWeakPtr(), base::Owned(params),
260 base::Owned(updated_local_ids), base::Owned(directory_changed),
261 base::Owned(should_copy_on_server)));
264 void CopyOperation::CopyAfterTryToCopyLocally(
265 const CopyParams* params,
266 const std::vector<std::string>* updated_local_ids,
267 const bool* directory_changed,
268 const bool* should_copy_on_server,
270 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
271 DCHECK(!params->callback.is_null());
273 for (size_t i = 0; i < updated_local_ids->size(); ++i)
274 observer_->OnEntryUpdatedByOperation((*updated_local_ids)[i]);
276 if (*directory_changed)
277 observer_->OnDirectoryChangedByOperation(params->dest_file_path.DirName());
279 if (error != FILE_ERROR_OK || !*should_copy_on_server) {
280 params->callback.Run(error);
284 base::FilePath new_title = params->dest_file_path.BaseName();
285 if (params->src_entry.file_specific_info().is_hosted_document()) {
286 // Drop the document extension, which should not be in the title.
287 // TODO(yoshiki): Remove this code with crbug.com/223304.
288 new_title = new_title.RemoveExtension();
291 base::Time last_modified =
292 params->preserve_last_modified ?
293 base::Time::FromInternalValue(
294 params->src_entry.file_info().last_modified()) : base::Time();
296 CopyResourceOnServer(
297 params->src_entry.resource_id(), params->parent_entry.resource_id(),
298 new_title.AsUTF8Unsafe(), last_modified, params->callback);
301 void CopyOperation::TransferFileFromLocalToRemote(
302 const base::FilePath& local_src_path,
303 const base::FilePath& remote_dest_path,
304 const FileOperationCallback& callback) {
305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
306 DCHECK(!callback.is_null());
308 std::string* gdoc_resource_id = new std::string;
309 std::string* parent_resource_id = new std::string;
310 base::PostTaskAndReplyWithResult(
311 blocking_task_runner_.get(),
314 &PrepareTransferFileFromLocalToRemote,
315 metadata_, local_src_path, remote_dest_path,
316 gdoc_resource_id, parent_resource_id),
318 &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
319 weak_ptr_factory_.GetWeakPtr(),
320 local_src_path, remote_dest_path, callback,
321 base::Owned(gdoc_resource_id), base::Owned(parent_resource_id)));
324 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
325 const base::FilePath& local_src_path,
326 const base::FilePath& remote_dest_path,
327 const FileOperationCallback& callback,
328 std::string* gdoc_resource_id,
329 std::string* parent_resource_id,
331 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
332 DCHECK(!callback.is_null());
334 if (error != FILE_ERROR_OK) {
339 // For regular files, schedule the transfer.
340 if (gdoc_resource_id->empty()) {
341 ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
345 // This is uploading a JSON file representing a hosted document.
346 // Copy the document on the Drive server.
348 // GDoc file may contain a resource ID in the old format.
349 const std::string canonicalized_resource_id =
350 id_canonicalizer_.Run(*gdoc_resource_id);
352 CopyResourceOnServer(
353 canonicalized_resource_id, *parent_resource_id,
354 // Drop the document extension, which should not be in the title.
355 // TODO(yoshiki): Remove this code with crbug.com/223304.
356 remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe(),
361 void CopyOperation::CopyResourceOnServer(
362 const std::string& resource_id,
363 const std::string& parent_resource_id,
364 const std::string& new_title,
365 const base::Time& last_modified,
366 const FileOperationCallback& callback) {
367 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
368 DCHECK(!callback.is_null());
370 scheduler_->CopyResource(
371 resource_id, parent_resource_id, new_title, last_modified,
372 base::Bind(&CopyOperation::CopyResourceOnServerAfterServerSideCopy,
373 weak_ptr_factory_.GetWeakPtr(),
377 void CopyOperation::CopyResourceOnServerAfterServerSideCopy(
378 const FileOperationCallback& callback,
379 google_apis::GDataErrorCode status,
380 scoped_ptr<google_apis::ResourceEntry> resource_entry) {
381 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
382 DCHECK(!callback.is_null());
384 FileError error = GDataToFileError(status);
385 if (error != FILE_ERROR_OK) {
390 // The copy on the server side is completed successfully. Update the local
392 base::FilePath* file_path = new base::FilePath;
393 base::PostTaskAndReplyWithResult(
394 blocking_task_runner_.get(),
396 base::Bind(&UpdateLocalStateForServerSideCopy,
397 metadata_, base::Passed(&resource_entry), file_path),
398 base::Bind(&CopyOperation::CopyResourceOnServerAfterUpdateLocalState,
399 weak_ptr_factory_.GetWeakPtr(),
400 callback, base::Owned(file_path)));
403 void CopyOperation::CopyResourceOnServerAfterUpdateLocalState(
404 const FileOperationCallback& callback,
405 base::FilePath* file_path,
407 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
408 DCHECK(!callback.is_null());
410 if (error == FILE_ERROR_OK)
411 observer_->OnDirectoryChangedByOperation(file_path->DirName());
415 void CopyOperation::ScheduleTransferRegularFile(
416 const base::FilePath& local_src_path,
417 const base::FilePath& remote_dest_path,
418 const FileOperationCallback& callback) {
419 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
420 DCHECK(!callback.is_null());
422 create_file_operation_->CreateFile(
424 false, // Not exclusive (OK even if a file already exists).
425 std::string(), // no specific mime type; CreateFile should guess it.
426 base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
427 weak_ptr_factory_.GetWeakPtr(),
428 local_src_path, remote_dest_path, callback));
431 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
432 const base::FilePath& local_src_path,
433 const base::FilePath& remote_dest_path,
434 const FileOperationCallback& callback,
436 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
437 DCHECK(!callback.is_null());
439 if (error != FILE_ERROR_OK) {
444 std::string* local_id = new std::string;
445 base::PostTaskAndReplyWithResult(
446 blocking_task_runner_.get(),
449 &UpdateLocalStateForScheduleTransfer,
450 metadata_, cache_, local_src_path, remote_dest_path, local_id),
452 &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
453 weak_ptr_factory_.GetWeakPtr(), callback, remote_dest_path,
454 base::Owned(local_id)));
457 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
458 const FileOperationCallback& callback,
459 const base::FilePath& remote_dest_path,
460 std::string* local_id,
462 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
463 DCHECK(!callback.is_null());
465 if (error == FILE_ERROR_OK) {
466 observer_->OnDirectoryChangedByOperation(remote_dest_path.DirName());
467 observer_->OnEntryUpdatedByOperation(*local_id);
472 } // namespace file_system