Upstream version 5.34.104.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 namespace {
38
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,
46                                                      &params->src_entry);
47   if (error != FILE_ERROR_OK)
48     return error;
49
50   error = metadata->GetResourceEntryByPath(params->dest_file_path.DirName(),
51                                            &params->parent_entry);
52   if (error != FILE_ERROR_OK)
53     return error;
54
55   if (!params->parent_entry.file_info().is_directory())
56     return FILE_ERROR_NOT_A_DIRECTORY;
57
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;
61
62   // Check destination.
63   ResourceEntry dest_entry;
64   error = metadata->GetResourceEntryByPath(params->dest_file_path, &dest_entry);
65   switch (error) {
66     case FILE_ERROR_OK:
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;
71
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)
76         return error;
77       updated_local_ids->push_back(dest_entry.local_id());
78       *directory_changed = true;
79       break;
80     case FILE_ERROR_NOT_FOUND:
81       break;
82     default:
83       return error;
84   }
85
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;
92     return FILE_ERROR_OK;
93   }
94
95   // Copy locally.
96   ResourceEntry entry;
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);
107
108   std::string local_id;
109   error = metadata->AddEntry(entry, &local_id);
110   if (error != FILE_ERROR_OK)
111     return error;
112   updated_local_ids->push_back(local_id);
113   *directory_changed = true;
114
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;
119   }
120
121   base::FilePath cache_file_path;
122   error = cache->GetFile(params->src_entry.local_id(), &cache_file_path);
123   if (error != FILE_ERROR_OK)
124     return error;
125
126   return cache->Store(local_id, std::string(), cache_file_path,
127                       internal::FileCache::FILE_OPERATION_COPY);
128 }
129
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);
136
137   ResourceEntry 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;
142
143   std::string parent_local_id;
144   FileError error = metadata->GetIdByResourceId(parent_resource_id,
145                                                 &parent_local_id);
146   if (error != FILE_ERROR_OK)
147     return error;
148   entry.set_parent_local_id(parent_local_id);
149
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);
156
157   if (error == FILE_ERROR_OK)
158     *file_path = metadata->GetFilePath(local_id);
159
160   return error;
161 }
162
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)
173     return error;
174
175   ResourceEntry entry;
176   error = metadata->GetResourceEntryById(*local_id, &entry);
177   if (error != FILE_ERROR_OK)
178     return error;
179
180   return cache->Store(*local_id, std::string(), local_src_path,
181                       internal::FileCache::FILE_OPERATION_COPY);
182 }
183
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)
196     return error;
197
198   // The destination's parent must be a directory.
199   if (!parent_entry.file_info().is_directory())
200     return FILE_ERROR_NOT_A_DIRECTORY;
201
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();
207   }
208
209   return FILE_ERROR_OK;
210 }
211
212 }  // namespace
213
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),
221     observer_(observer),
222     scheduler_(scheduler),
223     metadata_(metadata),
224     cache_(cache),
225     id_canonicalizer_(id_canonicalizer),
226     create_file_operation_(new CreateFileOperation(blocking_task_runner,
227                                                    observer,
228                                                    metadata)),
229     weak_ptr_factory_(this) {
230   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
231 }
232
233 CopyOperation::~CopyOperation() {
234   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
235 }
236
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());
243
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;
249
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(),
255       FROM_HERE,
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)));
262 }
263
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,
269     FileError error) {
270   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
271   DCHECK(!params->callback.is_null());
272
273   for (size_t i = 0; i < updated_local_ids->size(); ++i)
274     observer_->OnEntryUpdatedByOperation((*updated_local_ids)[i]);
275
276   if (*directory_changed)
277     observer_->OnDirectoryChangedByOperation(params->dest_file_path.DirName());
278
279   if (error != FILE_ERROR_OK || !*should_copy_on_server) {
280     params->callback.Run(error);
281     return;
282   }
283
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();
289   }
290
291   base::Time last_modified =
292       params->preserve_last_modified ?
293       base::Time::FromInternalValue(
294           params->src_entry.file_info().last_modified()) : base::Time();
295
296   CopyResourceOnServer(
297       params->src_entry.resource_id(), params->parent_entry.resource_id(),
298       new_title.AsUTF8Unsafe(), last_modified, params->callback);
299 }
300
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());
307
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(),
312       FROM_HERE,
313       base::Bind(
314           &PrepareTransferFileFromLocalToRemote,
315           metadata_, local_src_path, remote_dest_path,
316           gdoc_resource_id, parent_resource_id),
317       base::Bind(
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)));
322 }
323
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,
330     FileError error) {
331   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
332   DCHECK(!callback.is_null());
333
334   if (error != FILE_ERROR_OK) {
335     callback.Run(error);
336     return;
337   }
338
339   // For regular files, schedule the transfer.
340   if (gdoc_resource_id->empty()) {
341     ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback);
342     return;
343   }
344
345   // This is uploading a JSON file representing a hosted document.
346   // Copy the document on the Drive server.
347
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);
351
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(),
357       base::Time(),
358       callback);
359 }
360
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());
369
370   scheduler_->CopyResource(
371       resource_id, parent_resource_id, new_title, last_modified,
372       base::Bind(&CopyOperation::CopyResourceOnServerAfterServerSideCopy,
373                  weak_ptr_factory_.GetWeakPtr(),
374                  callback));
375 }
376
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());
383
384   FileError error = GDataToFileError(status);
385   if (error != FILE_ERROR_OK) {
386     callback.Run(error);
387     return;
388   }
389
390   // The copy on the server side is completed successfully. Update the local
391   // metadata.
392   base::FilePath* file_path = new base::FilePath;
393   base::PostTaskAndReplyWithResult(
394       blocking_task_runner_.get(),
395       FROM_HERE,
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)));
401 }
402
403 void CopyOperation::CopyResourceOnServerAfterUpdateLocalState(
404     const FileOperationCallback& callback,
405     base::FilePath* file_path,
406     FileError error) {
407   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
408   DCHECK(!callback.is_null());
409
410   if (error == FILE_ERROR_OK)
411     observer_->OnDirectoryChangedByOperation(file_path->DirName());
412   callback.Run(error);
413 }
414
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());
421
422   create_file_operation_->CreateFile(
423       remote_dest_path,
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));
429 }
430
431 void CopyOperation::ScheduleTransferRegularFileAfterCreate(
432     const base::FilePath& local_src_path,
433     const base::FilePath& remote_dest_path,
434     const FileOperationCallback& callback,
435     FileError error) {
436   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
437   DCHECK(!callback.is_null());
438
439   if (error != FILE_ERROR_OK) {
440     callback.Run(error);
441     return;
442   }
443
444   std::string* local_id = new std::string;
445   base::PostTaskAndReplyWithResult(
446       blocking_task_runner_.get(),
447       FROM_HERE,
448       base::Bind(
449           &UpdateLocalStateForScheduleTransfer,
450           metadata_, cache_, local_src_path, remote_dest_path, local_id),
451       base::Bind(
452           &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState,
453           weak_ptr_factory_.GetWeakPtr(), callback, remote_dest_path,
454           base::Owned(local_id)));
455 }
456
457 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState(
458     const FileOperationCallback& callback,
459     const base::FilePath& remote_dest_path,
460     std::string* local_id,
461     FileError error) {
462   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
463   DCHECK(!callback.is_null());
464
465   if (error == FILE_ERROR_OK) {
466     observer_->OnDirectoryChangedByOperation(remote_dest_path.DirName());
467     observer_->OnEntryUpdatedByOperation(*local_id);
468   }
469   callback.Run(error);
470 }
471
472 }  // namespace file_system
473 }  // namespace drive