Upstream version 6.35.121.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / drive / file_system / copy_operation.cc
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/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"
22
23 using content::BrowserThread;
24
25 namespace drive {
26 namespace file_system {
27
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;
35 };
36
37 // Enum for categorizing where a gdoc represented by a JSON file exists.
38 enum JsonGdocLocationType {
39   NOT_IN_METADATA,
40   IS_ORPHAN,
41   HAS_PARENT,
42 };
43
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)
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
69 namespace {
70
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,
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   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;
125   }
126
127   // Copy locally.
128   ResourceEntry entry;
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.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 (!cache_entry.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.
163 FileError UpdateLocalStateForServerSideOperation(
164     internal::ResourceMetadata* metadata,
165     scoped_ptr<google_apis::ResourceEntry> resource_entry,
166     base::FilePath* file_path) {
167   DCHECK(resource_entry);
168
169   ResourceEntry entry;
170   std::string parent_resource_id;
171   if (!ConvertToResourceEntry(*resource_entry, &entry, &parent_resource_id) ||
172       parent_resource_id.empty())
173     return FILE_ERROR_NOT_A_FILE;
174
175   std::string parent_local_id;
176   FileError error = metadata->GetIdByResourceId(parent_resource_id,
177                                                 &parent_local_id);
178   if (error != FILE_ERROR_OK)
179     return error;
180   entry.set_parent_local_id(parent_local_id);
181
182   std::string local_id;
183   error = metadata->AddEntry(entry, &local_id);
184   // Depending on timing, the metadata may have inserted via change list
185   // already. So, FILE_ERROR_EXISTS is not an error.
186   if (error == FILE_ERROR_EXISTS)
187     error = metadata->GetIdByResourceId(entry.resource_id(), &local_id);
188
189   if (error == FILE_ERROR_OK)
190     *file_path = metadata->GetFilePath(local_id);
191
192   return error;
193 }
194
195 // Stores the file at |local_file_path| to the cache as a content of entry at
196 // |remote_dest_path|, and marks it dirty.
197 FileError UpdateLocalStateForScheduleTransfer(
198     internal::ResourceMetadata* metadata,
199     internal::FileCache* cache,
200     const base::FilePath& local_src_path,
201     const base::FilePath& remote_dest_path,
202     std::string* local_id) {
203   FileError error = metadata->GetIdByPath(remote_dest_path, local_id);
204   if (error != FILE_ERROR_OK)
205     return error;
206
207   ResourceEntry entry;
208   error = metadata->GetResourceEntryById(*local_id, &entry);
209   if (error != FILE_ERROR_OK)
210     return error;
211
212   return cache->Store(*local_id, std::string(), local_src_path,
213                       internal::FileCache::FILE_OPERATION_COPY);
214 }
215
216 // Gets the file size of the |local_path|, and the ResourceEntry for the parent
217 // of |remote_path| to prepare the necessary information for transfer.
218 FileError PrepareTransferFileFromLocalToRemote(
219     internal::ResourceMetadata* metadata,
220     const base::FilePath& local_src_path,
221     const base::FilePath& remote_dest_path,
222     std::string* gdoc_resource_id,
223     ResourceEntry* parent_entry) {
224   FileError error = metadata->GetResourceEntryByPath(
225       remote_dest_path.DirName(), parent_entry);
226   if (error != FILE_ERROR_OK)
227     return error;
228
229   // The destination's parent must be a directory.
230   if (!parent_entry->file_info().is_directory())
231     return FILE_ERROR_NOT_A_DIRECTORY;
232
233   // Try to parse GDoc File and extract the resource id, if necessary.
234   // Failing isn't problem. It'd be handled as a regular file, then.
235   if (util::HasGDocFileExtension(local_src_path))
236     *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path);
237   return FILE_ERROR_OK;
238 }
239
240 // Performs local work before server-side work for transferring JSON-represented
241 // gdoc files.
242 FileError LocalWorkForTransferJsonGdocFile(
243     internal::ResourceMetadata* metadata,
244     CopyOperation::TransferJsonGdocParams* params) {
245   std::string local_id;
246   FileError error = metadata->GetIdByResourceId(params->resource_id, &local_id);
247   if (error != FILE_ERROR_OK) {
248     params->location_type = NOT_IN_METADATA;
249     return error == FILE_ERROR_NOT_FOUND ? FILE_ERROR_OK : error;
250   }
251
252   ResourceEntry entry;
253   error = metadata->GetResourceEntryById(local_id, &entry);
254   if (error != FILE_ERROR_OK)
255     return error;
256   params->local_id = entry.local_id();
257
258   if (entry.parent_local_id() == util::kDriveOtherDirLocalId) {
259     params->location_type = IS_ORPHAN;
260     entry.set_title(params->new_title);
261     entry.set_parent_local_id(params->parent_local_id);
262     entry.set_metadata_edit_state(ResourceEntry::DIRTY);
263     error = metadata->RefreshEntry(entry);
264     if (error == FILE_ERROR_OK)
265       params->changed_path = metadata->GetFilePath(local_id);
266     return error;
267   }
268
269   params->location_type = HAS_PARENT;
270   return FILE_ERROR_OK;
271 }
272
273 }  // namespace
274
275 CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
276                              OperationObserver* observer,
277                              JobScheduler* scheduler,
278                              internal::ResourceMetadata* metadata,
279                              internal::FileCache* cache,
280                              const ResourceIdCanonicalizer& id_canonicalizer)
281   : blocking_task_runner_(blocking_task_runner),
282     observer_(observer),
283     scheduler_(scheduler),
284     metadata_(metadata),
285     cache_(cache),
286     id_canonicalizer_(id_canonicalizer),
287     create_file_operation_(new CreateFileOperation(blocking_task_runner,
288                                                    observer,
289                                                    metadata)),
290     weak_ptr_factory_(this) {
291   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
292 }
293
294 CopyOperation::~CopyOperation() {
295   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
296 }
297
298 void 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
325 void 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     observer_->OnEntryUpdatedByOperation((*updated_local_ids)[i]);
336
337   if (*directory_changed)
338     observer_->OnDirectoryChangedByOperation(params->dest_file_path.DirName());
339
340   if (error != FILE_ERROR_OK || !*should_copy_on_server) {
341     params->callback.Run(error);
342     return;
343   }
344
345   base::FilePath new_title = params->dest_file_path.BaseName();
346   if (params->src_entry.file_specific_info().is_hosted_document()) {
347     // Drop the document extension, which should not be in the title.
348     // TODO(yoshiki): Remove this code with crbug.com/223304.
349     new_title = new_title.RemoveExtension();
350   }
351
352   base::Time last_modified =
353       params->preserve_last_modified ?
354       base::Time::FromInternalValue(
355           params->src_entry.file_info().last_modified()) : base::Time();
356
357   CopyResourceOnServer(
358       params->src_entry.resource_id(), params->parent_entry.resource_id(),
359       new_title.AsUTF8Unsafe(), last_modified, params->callback);
360 }
361
362 void CopyOperation::TransferFileFromLocalToRemote(
363     const base::FilePath& local_src_path,
364     const base::FilePath& remote_dest_path,
365     const FileOperationCallback& callback) {
366   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
367   DCHECK(!callback.is_null());
368
369   std::string* gdoc_resource_id = new std::string;
370   ResourceEntry* parent_entry = new ResourceEntry;
371   base::PostTaskAndReplyWithResult(
372       blocking_task_runner_.get(),
373       FROM_HERE,
374       base::Bind(
375           &PrepareTransferFileFromLocalToRemote,
376           metadata_, local_src_path, remote_dest_path,
377           gdoc_resource_id, parent_entry),
378       base::Bind(
379           &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare,
380           weak_ptr_factory_.GetWeakPtr(),
381           local_src_path, remote_dest_path, callback,
382           base::Owned(gdoc_resource_id), base::Owned(parent_entry)));
383 }
384
385 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare(
386     const base::FilePath& local_src_path,
387     const base::FilePath& remote_dest_path,
388     const FileOperationCallback& callback,
389     std::string* gdoc_resource_id,
390     ResourceEntry* parent_entry,
391     FileError error) {
392   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
393   DCHECK(!callback.is_null());
394
395   if (error != FILE_ERROR_OK) {
396     callback.Run(error);
397     return;
398   }
399
400   // For regular files, schedule the transfer.
401   if (gdoc_resource_id->empty()) {
402     ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
403     return;
404   }
405
406   // GDoc file may contain a resource ID in the old format.
407   const std::string canonicalized_resource_id =
408       id_canonicalizer_.Run(*gdoc_resource_id);
409
410   // Drop the document extension, which should not be in the title.
411   // TODO(yoshiki): Remove this code with crbug.com/223304.
412   const std::string new_title =
413       remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe();
414
415   // This is uploading a JSON file representing a hosted document.
416   TransferJsonGdocParams* params = new TransferJsonGdocParams(
417       callback, canonicalized_resource_id, *parent_entry, new_title);
418   base::PostTaskAndReplyWithResult(
419       blocking_task_runner_.get(),
420       FROM_HERE,
421       base::Bind(&LocalWorkForTransferJsonGdocFile, metadata_, params),
422       base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork,
423                  weak_ptr_factory_.GetWeakPtr(), base::Owned(params)));
424 }
425
426 void CopyOperation::TransferJsonGdocFileAfterLocalWork(
427     TransferJsonGdocParams* params,
428     FileError error) {
429   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
430
431   if (error != FILE_ERROR_OK) {
432     params->callback.Run(error);
433     return;
434   }
435
436   switch (params->location_type) {
437     // When |resource_id| is found in the local metadata and it has a specific
438     // parent folder, we assume the user's intention is to copy the document and
439     // thus perform the server-side copy operation.
440     case HAS_PARENT:
441       CopyResourceOnServer(params->resource_id,
442                            params->parent_resource_id,
443                            params->new_title,
444                            base::Time(),
445                            params->callback);
446       break;
447     // When |resource_id| has no parent, we just set the new destination folder
448     // as the parent, for sharing the document between the original source.
449     // This reparenting is already done in LocalWorkForTransferJsonGdocFile().
450     case IS_ORPHAN:
451       DCHECK(!params->changed_path.empty());
452       observer_->OnEntryUpdatedByOperation(params->local_id);
453       observer_->OnDirectoryChangedByOperation(params->changed_path.DirName());
454       params->callback.Run(error);
455       break;
456     // When the |resource_id| is not in the local metadata, assume it to be a
457     // document just now shared on the server but not synced locally.
458     // Same as the IS_ORPHAN case, we want to deal the case by setting parent,
459     // but this time we need to resort to server side operation.
460     case NOT_IN_METADATA:
461       scheduler_->UpdateResource(
462           params->resource_id,
463           params->parent_resource_id,
464           params->new_title,
465           base::Time(),
466           base::Time(),
467           ClientContext(USER_INITIATED),
468           base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
469                      weak_ptr_factory_.GetWeakPtr(),
470                      params->callback));
471       break;
472   }
473 }
474
475 void CopyOperation::CopyResourceOnServer(
476     const std::string& resource_id,
477     const std::string& parent_resource_id,
478     const std::string& new_title,
479     const base::Time& last_modified,
480     const FileOperationCallback& callback) {
481   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
482   DCHECK(!callback.is_null());
483
484   scheduler_->CopyResource(
485       resource_id, parent_resource_id, new_title, last_modified,
486       base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
487                  weak_ptr_factory_.GetWeakPtr(),
488                  callback));
489 }
490
491 void CopyOperation::UpdateAfterServerSideOperation(
492     const FileOperationCallback& callback,
493     google_apis::GDataErrorCode status,
494     scoped_ptr<google_apis::ResourceEntry> resource_entry) {
495   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
496   DCHECK(!callback.is_null());
497
498   FileError error = GDataToFileError(status);
499   if (error != FILE_ERROR_OK) {
500     callback.Run(error);
501     return;
502   }
503
504   // The copy on the server side is completed successfully. Update the local
505   // metadata.
506   base::FilePath* file_path = new base::FilePath;
507   base::PostTaskAndReplyWithResult(
508       blocking_task_runner_.get(),
509       FROM_HERE,
510       base::Bind(&UpdateLocalStateForServerSideOperation,
511                  metadata_, base::Passed(&resource_entry), file_path),
512       base::Bind(&CopyOperation::UpdateAfterLocalStateUpdate,
513                  weak_ptr_factory_.GetWeakPtr(),
514                  callback, base::Owned(file_path)));
515 }
516
517 void CopyOperation::UpdateAfterLocalStateUpdate(
518     const FileOperationCallback& callback,
519     base::FilePath* file_path,
520     FileError error) {
521   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
522   DCHECK(!callback.is_null());
523
524   if (error == FILE_ERROR_OK)
525     observer_->OnDirectoryChangedByOperation(file_path->DirName());
526   callback.Run(error);
527 }
528
529 void CopyOperation::ScheduleTransferRegularFile(
530     const base::FilePath& local_src_path,
531     const base::FilePath& remote_dest_path,
532     const FileOperationCallback& callback) {
533   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
534   DCHECK(!callback.is_null());
535
536   create_file_operation_->CreateFile(
537       remote_dest_path,
538       false,  // Not exclusive (OK even if a file already exists).
539       std::string(),  // no specific mime type; CreateFile should guess it.
540       base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
541                  weak_ptr_factory_.GetWeakPtr(),
542                  local_src_path, remote_dest_path, callback));
543 }
544
545 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
546     const base::FilePath& local_src_path,
547     const base::FilePath& remote_dest_path,
548     const FileOperationCallback& callback,
549     FileError error) {
550   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
551   DCHECK(!callback.is_null());
552
553   if (error != FILE_ERROR_OK) {
554     callback.Run(error);
555     return;
556   }
557
558   std::string* local_id = new std::string;
559   base::PostTaskAndReplyWithResult(
560       blocking_task_runner_.get(),
561       FROM_HERE,
562       base::Bind(
563           &UpdateLocalStateForScheduleTransfer,
564           metadata_, cache_, local_src_path, remote_dest_path, local_id),
565       base::Bind(
566           &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
567           weak_ptr_factory_.GetWeakPtr(), callback, remote_dest_path,
568           base::Owned(local_id)));
569 }
570
571 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
572     const FileOperationCallback& callback,
573     const base::FilePath& remote_dest_path,
574     std::string* local_id,
575     FileError error) {
576   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
577   DCHECK(!callback.is_null());
578
579   if (error == FILE_ERROR_OK) {
580     observer_->OnDirectoryChangedByOperation(remote_dest_path.DirName());
581     observer_->OnEntryUpdatedByOperation(*local_id);
582   }
583   callback.Run(error);
584 }
585
586 }  // namespace file_system
587 }  // namespace drive