Upstream version 7.36.149.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.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);
140
141   std::string local_id;
142   error = metadata->AddEntry(entry, &local_id);
143   if (error != FILE_ERROR_OK)
144     return error;
145   updated_local_ids->push_back(local_id);
146   *directory_changed = true;
147
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;
152   }
153
154   base::FilePath cache_file_path;
155   error = cache->GetFile(params->src_entry.local_id(), &cache_file_path);
156   if (error != FILE_ERROR_OK)
157     return error;
158
159   return cache->Store(local_id, std::string(), cache_file_path,
160                       internal::FileCache::FILE_OPERATION_COPY);
161 }
162
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);
169
170   ResourceEntry 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;
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     *file_path = metadata->GetFilePath(local_id);
192
193   return error;
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.
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)
206     return error;
207
208   ResourceEntry entry;
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.
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)
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::HasGDocFileExtension(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.
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;
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       params->changed_path = metadata->GetFilePath(local_id);
268     return error;
269   }
270
271   params->location_type = HAS_PARENT;
272   return FILE_ERROR_OK;
273 }
274
275 }  // namespace
276
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),
284     observer_(observer),
285     scheduler_(scheduler),
286     metadata_(metadata),
287     cache_(cache),
288     id_canonicalizer_(id_canonicalizer),
289     create_file_operation_(new CreateFileOperation(blocking_task_runner,
290                                                    observer,
291                                                    metadata)),
292     weak_ptr_factory_(this) {
293   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
294 }
295
296 CopyOperation::~CopyOperation() {
297   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
298 }
299
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());
306
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;
312
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(),
318       FROM_HERE,
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)));
325 }
326
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,
332     FileError error) {
333   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
334   DCHECK(!params->callback.is_null());
335
336   for (size_t i = 0; i < updated_local_ids->size(); ++i)
337     observer_->OnEntryUpdatedByOperation((*updated_local_ids)[i]);
338
339   if (*directory_changed)
340     observer_->OnDirectoryChangedByOperation(params->dest_file_path.DirName());
341
342   if (error != FILE_ERROR_OK || !*should_copy_on_server) {
343     params->callback.Run(error);
344     return;
345   }
346
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();
352   }
353
354   base::Time last_modified =
355       params->preserve_last_modified ?
356       base::Time::FromInternalValue(
357           params->src_entry.file_info().last_modified()) : base::Time();
358
359   CopyResourceOnServer(
360       params->src_entry.resource_id(), params->parent_entry.resource_id(),
361       new_title.AsUTF8Unsafe(), last_modified, params->callback);
362 }
363
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());
370
371   std::string* gdoc_resource_id = new std::string;
372   ResourceEntry* parent_entry = new ResourceEntry;
373   base::PostTaskAndReplyWithResult(
374       blocking_task_runner_.get(),
375       FROM_HERE,
376       base::Bind(
377           &PrepareTransferFileFromLocalToRemote,
378           metadata_, local_src_path, remote_dest_path,
379           gdoc_resource_id, parent_entry),
380       base::Bind(
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)));
385 }
386
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,
393     FileError error) {
394   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
395   DCHECK(!callback.is_null());
396
397   if (error != FILE_ERROR_OK) {
398     callback.Run(error);
399     return;
400   }
401
402   // For regular files, schedule the transfer.
403   if (gdoc_resource_id->empty()) {
404     ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
405     return;
406   }
407
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);
411
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();
416
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(),
422       FROM_HERE,
423       base::Bind(&LocalWorkForTransferJsonGdocFile, metadata_, params),
424       base::Bind(&CopyOperation::TransferJsonGdocFileAfterLocalWork,
425                  weak_ptr_factory_.GetWeakPtr(), base::Owned(params)));
426 }
427
428 void CopyOperation::TransferJsonGdocFileAfterLocalWork(
429     TransferJsonGdocParams* params,
430     FileError error) {
431   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
432
433   if (error != FILE_ERROR_OK) {
434     params->callback.Run(error);
435     return;
436   }
437
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.
442     case HAS_PARENT:
443       CopyResourceOnServer(params->resource_id,
444                            params->parent_resource_id,
445                            params->new_title,
446                            base::Time(),
447                            params->callback);
448       break;
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().
452     case IS_ORPHAN:
453       DCHECK(!params->changed_path.empty());
454       observer_->OnEntryUpdatedByOperation(params->local_id);
455       observer_->OnDirectoryChangedByOperation(params->changed_path.DirName());
456       params->callback.Run(error);
457       break;
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(
464           params->resource_id,
465           params->parent_resource_id,
466           params->new_title,
467           base::Time(),
468           base::Time(),
469           ClientContext(USER_INITIATED),
470           base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
471                      weak_ptr_factory_.GetWeakPtr(),
472                      params->callback));
473       break;
474   }
475 }
476
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());
485
486   scheduler_->CopyResource(
487       resource_id, parent_resource_id, new_title, last_modified,
488       base::Bind(&CopyOperation::UpdateAfterServerSideOperation,
489                  weak_ptr_factory_.GetWeakPtr(),
490                  callback));
491 }
492
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());
499
500   FileError error = GDataToFileError(status);
501   if (error != FILE_ERROR_OK) {
502     callback.Run(error);
503     return;
504   }
505
506   // The copy on the server side is completed successfully. Update the local
507   // metadata.
508   base::FilePath* file_path = new base::FilePath;
509   base::PostTaskAndReplyWithResult(
510       blocking_task_runner_.get(),
511       FROM_HERE,
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)));
517 }
518
519 void CopyOperation::UpdateAfterLocalStateUpdate(
520     const FileOperationCallback& callback,
521     base::FilePath* file_path,
522     FileError error) {
523   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
524   DCHECK(!callback.is_null());
525
526   if (error == FILE_ERROR_OK)
527     observer_->OnDirectoryChangedByOperation(file_path->DirName());
528   callback.Run(error);
529 }
530
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());
537
538   create_file_operation_->CreateFile(
539       remote_dest_path,
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));
545 }
546
547 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
548     const base::FilePath& local_src_path,
549     const base::FilePath& remote_dest_path,
550     const FileOperationCallback& callback,
551     FileError error) {
552   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
553   DCHECK(!callback.is_null());
554
555   if (error != FILE_ERROR_OK) {
556     callback.Run(error);
557     return;
558   }
559
560   std::string* local_id = new std::string;
561   base::PostTaskAndReplyWithResult(
562       blocking_task_runner_.get(),
563       FROM_HERE,
564       base::Bind(
565           &UpdateLocalStateForScheduleTransfer,
566           metadata_, cache_, local_src_path, remote_dest_path, local_id),
567       base::Bind(
568           &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
569           weak_ptr_factory_.GetWeakPtr(), callback, remote_dest_path,
570           base::Owned(local_id)));
571 }
572
573 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
574     const FileOperationCallback& callback,
575     const base::FilePath& remote_dest_path,
576     std::string* local_id,
577     FileError error) {
578   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
579   DCHECK(!callback.is_null());
580
581   if (error == FILE_ERROR_OK) {
582     observer_->OnDirectoryChangedByOperation(remote_dest_path.DirName());
583     observer_->OnEntryUpdatedByOperation(*local_id);
584   }
585   callback.Run(error);
586 }
587
588 }  // namespace file_system
589 }  // namespace drive