copy_operation.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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/download_operation.h"
15#include "chrome/browser/chromeos/drive/file_system/move_operation.h"
16#include "chrome/browser/chromeos/drive/file_system/operation_observer.h"
17#include "chrome/browser/chromeos/drive/file_system_util.h"
18#include "chrome/browser/chromeos/drive/job_scheduler.h"
19#include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
20#include "chrome/browser/drive/drive_api_util.h"
21#include "content/public/browser/browser_thread.h"
22
23using content::BrowserThread;
24
25namespace drive {
26namespace file_system {
27
28namespace {
29
30// Copies a file from |src_file_path| to |dest_file_path| on the local
31// file system using base::CopyFile.
32// Returns FILE_ERROR_OK on success or FILE_ERROR_FAILED otherwise.
33FileError CopyLocalFileOnBlockingPool(
34    const base::FilePath& src_file_path,
35    const base::FilePath& dest_file_path) {
36  return base::CopyFile(src_file_path, dest_file_path) ?
37      FILE_ERROR_OK : FILE_ERROR_FAILED;
38}
39
40// Stores a file to the cache and mark it dirty.
41FileError StoreAndMarkDirty(internal::FileCache* cache,
42                            const std::string& resource_id,
43                            const std::string& md5,
44                            const base::FilePath& local_file_path) {
45  FileError error = cache->Store(resource_id, md5, local_file_path,
46                                 internal::FileCache::FILE_OPERATION_COPY);
47  if (error != FILE_ERROR_OK)
48    return error;
49  return cache->MarkDirty(resource_id, md5);
50}
51
52}  // namespace
53
54CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner,
55                             OperationObserver* observer,
56                             JobScheduler* scheduler,
57                             internal::ResourceMetadata* metadata,
58                             internal::FileCache* cache,
59                             DriveServiceInterface* drive_service,
60                             const base::FilePath& temporary_file_directory)
61  : blocking_task_runner_(blocking_task_runner),
62    observer_(observer),
63    scheduler_(scheduler),
64    metadata_(metadata),
65    cache_(cache),
66    drive_service_(drive_service),
67    create_file_operation_(new CreateFileOperation(blocking_task_runner,
68                                                   observer,
69                                                   scheduler,
70                                                   metadata,
71                                                   cache)),
72    download_operation_(new DownloadOperation(blocking_task_runner,
73                                              observer,
74                                              scheduler,
75                                              metadata,
76                                              cache,
77                                              temporary_file_directory)),
78    move_operation_(new MoveOperation(observer, scheduler, metadata)),
79    weak_ptr_factory_(this) {
80  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
81}
82
83CopyOperation::~CopyOperation() {
84  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
85}
86
87void CopyOperation::Copy(const base::FilePath& src_file_path,
88                         const base::FilePath& dest_file_path,
89                         const FileOperationCallback& callback) {
90  BrowserThread::CurrentlyOn(BrowserThread::UI);
91  DCHECK(!callback.is_null());
92
93  metadata_->GetResourceEntryPairByPathsOnUIThread(
94      src_file_path,
95      dest_file_path.DirName(),
96      base::Bind(&CopyOperation::CopyAfterGetResourceEntryPair,
97                 weak_ptr_factory_.GetWeakPtr(),
98                 dest_file_path,
99                 callback));
100}
101
102void CopyOperation::TransferFileFromRemoteToLocal(
103    const base::FilePath& remote_src_file_path,
104    const base::FilePath& local_dest_file_path,
105    const FileOperationCallback& callback) {
106  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
107  DCHECK(!callback.is_null());
108
109  download_operation_->EnsureFileDownloadedByPath(
110      remote_src_file_path,
111      ClientContext(USER_INITIATED),
112      GetFileContentInitializedCallback(),
113      google_apis::GetContentCallback(),
114      base::Bind(&CopyOperation::OnGetFileCompleteForTransferFile,
115                 weak_ptr_factory_.GetWeakPtr(),
116                 local_dest_file_path,
117                 callback));
118}
119
120void CopyOperation::OnGetFileCompleteForTransferFile(
121    const base::FilePath& local_dest_file_path,
122    const FileOperationCallback& callback,
123    FileError error,
124    const base::FilePath& local_file_path,
125    scoped_ptr<ResourceEntry> entry) {
126  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
127  DCHECK(!callback.is_null());
128
129  if (error != FILE_ERROR_OK) {
130    callback.Run(error);
131    return;
132  }
133
134  // GetFileByPath downloads the file from Drive to a local cache, which is then
135  // copied to the actual destination path on the local file system using
136  // CopyLocalFileOnBlockingPool.
137  base::PostTaskAndReplyWithResult(
138      blocking_task_runner_.get(),
139      FROM_HERE,
140      base::Bind(
141          &CopyLocalFileOnBlockingPool, local_file_path, local_dest_file_path),
142      callback);
143}
144
145void CopyOperation::TransferFileFromLocalToRemote(
146    const base::FilePath& local_src_file_path,
147    const base::FilePath& remote_dest_file_path,
148    const FileOperationCallback& callback) {
149  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
150  DCHECK(!callback.is_null());
151
152  // Make sure the destination directory exists.
153  metadata_->GetResourceEntryByPathOnUIThread(
154      remote_dest_file_path.DirName(),
155      base::Bind(
156          &CopyOperation::TransferFileFromLocalToRemoteAfterGetResourceEntry,
157          weak_ptr_factory_.GetWeakPtr(),
158          local_src_file_path,
159          remote_dest_file_path,
160          callback));
161}
162
163void CopyOperation::ScheduleTransferRegularFile(
164    const base::FilePath& local_file_path,
165    const base::FilePath& remote_dest_file_path,
166    const FileOperationCallback& callback) {
167  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
168  DCHECK(!callback.is_null());
169
170  const bool fail_if_file_already_exists = true;
171  create_file_operation_->CreateFile(
172      remote_dest_file_path,
173      fail_if_file_already_exists,
174      base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate,
175                 weak_ptr_factory_.GetWeakPtr(),
176                 local_file_path,
177                 remote_dest_file_path,
178                 callback));
179}
180
181void CopyOperation::ScheduleTransferRegularFileAfterCreate(
182    const base::FilePath& local_file_path,
183    const base::FilePath& remote_dest_file_path,
184    const FileOperationCallback& callback,
185    FileError error) {
186  if (error != FILE_ERROR_OK) {
187    callback.Run(error);
188    return;
189  }
190
191  metadata_->GetResourceEntryByPathOnUIThread(
192      remote_dest_file_path,
193      base::Bind(
194          &CopyOperation::ScheduleTransferRegularFileAfterGetResourceEntry,
195          weak_ptr_factory_.GetWeakPtr(),
196          local_file_path,
197          callback));
198}
199
200void CopyOperation::ScheduleTransferRegularFileAfterGetResourceEntry(
201    const base::FilePath& local_file_path,
202    const FileOperationCallback& callback,
203    FileError error,
204    scoped_ptr<ResourceEntry> entry) {
205  if (error != FILE_ERROR_OK) {
206    callback.Run(error);
207    return;
208  }
209
210  ResourceEntry* entry_ptr = entry.get();
211  base::PostTaskAndReplyWithResult(
212      blocking_task_runner_.get(),
213      FROM_HERE,
214      base::Bind(&StoreAndMarkDirty,
215                 cache_,
216                 entry_ptr->resource_id(),
217                 entry_ptr->file_specific_info().md5(),
218                 local_file_path),
219      base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterStore,
220                 weak_ptr_factory_.GetWeakPtr(),
221                 base::Passed(&entry),
222                 callback));
223}
224
225void CopyOperation::ScheduleTransferRegularFileAfterStore(
226    scoped_ptr<ResourceEntry> entry,
227    const FileOperationCallback& callback,
228    FileError error) {
229  if (error == FILE_ERROR_OK)
230    observer_->OnCacheFileUploadNeededByOperation(entry->resource_id());
231  callback.Run(error);
232}
233
234void CopyOperation::CopyHostedDocumentToDirectory(
235    const base::FilePath& dir_path,
236    const std::string& resource_id,
237    const base::FilePath::StringType& new_name,
238    const FileOperationCallback& callback) {
239  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
240  DCHECK(!callback.is_null());
241
242  scheduler_->CopyHostedDocument(
243      resource_id,
244      base::FilePath(new_name).AsUTF8Unsafe(),
245      base::Bind(&CopyOperation::OnCopyHostedDocumentCompleted,
246                 weak_ptr_factory_.GetWeakPtr(),
247                 dir_path,
248                 callback));
249}
250
251void CopyOperation::OnCopyHostedDocumentCompleted(
252    const base::FilePath& dir_path,
253    const FileOperationCallback& callback,
254    google_apis::GDataErrorCode status,
255    scoped_ptr<google_apis::ResourceEntry> resource_entry) {
256  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
257  DCHECK(!callback.is_null());
258
259  FileError error = util::GDataToFileError(status);
260  if (error != FILE_ERROR_OK) {
261    callback.Run(error);
262    return;
263  }
264  DCHECK(resource_entry);
265
266  // The entry was added in the root directory on the server, so we should
267  // first add it to the root to mirror the state and then move it to the
268  // destination directory by MoveEntryFromRootDirectory().
269  metadata_->AddEntryOnUIThread(
270      ConvertToResourceEntry(*resource_entry),
271      base::Bind(&CopyOperation::MoveEntryFromRootDirectory,
272                 weak_ptr_factory_.GetWeakPtr(),
273                 dir_path,
274                 callback));
275}
276
277void CopyOperation::MoveEntryFromRootDirectory(
278    const base::FilePath& directory_path,
279    const FileOperationCallback& callback,
280    FileError error,
281    const base::FilePath& file_path) {
282  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
283  DCHECK(!callback.is_null());
284  DCHECK_EQ(util::GetDriveMyDriveRootPath().value(),
285            file_path.DirName().value());
286
287  // Return if there is an error or |dir_path| is the root directory.
288  if (error != FILE_ERROR_OK ||
289      directory_path == util::GetDriveMyDriveRootPath()) {
290    callback.Run(error);
291    return;
292  }
293
294  move_operation_->Move(file_path,
295                        directory_path.Append(file_path.BaseName()),
296                        callback);
297}
298
299void CopyOperation::CopyAfterGetResourceEntryPair(
300    const base::FilePath& dest_file_path,
301    const FileOperationCallback& callback,
302    scoped_ptr<EntryInfoPairResult> result) {
303  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
304  DCHECK(!callback.is_null());
305  DCHECK(result.get());
306
307  if (result->first.error != FILE_ERROR_OK) {
308    callback.Run(result->first.error);
309    return;
310  } else if (result->second.error != FILE_ERROR_OK) {
311    callback.Run(result->second.error);
312    return;
313  }
314
315  scoped_ptr<ResourceEntry> src_file_proto = result->first.entry.Pass();
316  scoped_ptr<ResourceEntry> dest_parent_proto = result->second.entry.Pass();
317
318  if (!dest_parent_proto->file_info().is_directory()) {
319    callback.Run(FILE_ERROR_NOT_A_DIRECTORY);
320    return;
321  } else if (src_file_proto->file_info().is_directory()) {
322    // TODO(kochi): Implement copy for directories. In the interim,
323    // we handle recursive directory copy in the file manager.
324    // crbug.com/141596
325    callback.Run(FILE_ERROR_INVALID_OPERATION);
326    return;
327  }
328
329  // If Drive API v2 is enabled, we can copy resources on server side.
330  if (util::IsDriveV2ApiEnabled()) {
331    base::FilePath new_title = dest_file_path.BaseName();
332    if (src_file_proto->file_specific_info().is_hosted_document()) {
333      // Drop the document extension, which should not be in the title.
334      // TODO(yoshiki): Remove this code with crbug.com/223304.
335      new_title = new_title.RemoveExtension();
336    }
337
338    scheduler_->CopyResource(
339        src_file_proto->resource_id(),
340        dest_parent_proto->resource_id(),
341        new_title.value(),
342        base::Bind(&CopyOperation::OnCopyResourceCompleted,
343                   weak_ptr_factory_.GetWeakPtr(), callback));
344    return;
345  }
346
347
348  if (src_file_proto->file_specific_info().is_hosted_document()) {
349    CopyHostedDocumentToDirectory(
350        dest_file_path.DirName(),
351        src_file_proto->resource_id(),
352        // Drop the document extension, which should not be in the title.
353        // TODO(yoshiki): Remove this code with crbug.com/223304.
354        dest_file_path.BaseName().RemoveExtension().value(),
355        callback);
356    return;
357  }
358
359  const base::FilePath& src_file_path = result->first.path;
360  download_operation_->EnsureFileDownloadedByPath(
361      src_file_path,
362      ClientContext(USER_INITIATED),
363      GetFileContentInitializedCallback(),
364      google_apis::GetContentCallback(),
365      base::Bind(&CopyOperation::OnGetFileCompleteForCopy,
366                 weak_ptr_factory_.GetWeakPtr(),
367                 dest_file_path,
368                 callback));
369}
370
371void CopyOperation::OnCopyResourceCompleted(
372    const FileOperationCallback& callback,
373    google_apis::GDataErrorCode status,
374    scoped_ptr<google_apis::ResourceEntry> resource_entry) {
375  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
376  DCHECK(!callback.is_null());
377
378  FileError error = util::GDataToFileError(status);
379  if (error != FILE_ERROR_OK) {
380    callback.Run(error);
381    return;
382  }
383  DCHECK(resource_entry);
384
385  // The copy on the server side is completed successfully. Update the local
386  // metadata.
387  base::PostTaskAndReplyWithResult(
388      blocking_task_runner_.get(),
389      FROM_HERE,
390      base::Bind(&internal::ResourceMetadata::AddEntry,
391                 base::Unretained(metadata_),
392                 ConvertToResourceEntry(*resource_entry)),
393      callback);
394}
395
396void CopyOperation::OnGetFileCompleteForCopy(
397    const base::FilePath& remote_dest_file_path,
398    const FileOperationCallback& callback,
399    FileError error,
400    const base::FilePath& local_file_path,
401    scoped_ptr<ResourceEntry> entry) {
402  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
403  DCHECK(!callback.is_null());
404
405  if (error != FILE_ERROR_OK) {
406    callback.Run(error);
407    return;
408  }
409
410  // This callback is only triggered for a regular file via Copy().
411  DCHECK(entry && !entry->file_specific_info().is_hosted_document());
412  ScheduleTransferRegularFile(local_file_path, remote_dest_file_path, callback);
413}
414
415void CopyOperation::TransferFileFromLocalToRemoteAfterGetResourceEntry(
416    const base::FilePath& local_src_file_path,
417    const base::FilePath& remote_dest_file_path,
418    const FileOperationCallback& callback,
419    FileError error,
420    scoped_ptr<ResourceEntry> entry) {
421  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
422  DCHECK(!callback.is_null());
423
424  if (error != FILE_ERROR_OK) {
425    callback.Run(error);
426    return;
427  }
428
429  DCHECK(entry.get());
430  if (!entry->file_info().is_directory()) {
431    // The parent of |remote_dest_file_path| is not a directory.
432    callback.Run(FILE_ERROR_NOT_A_DIRECTORY);
433    return;
434  }
435
436  if (util::HasGDocFileExtension(local_src_file_path)) {
437    base::PostTaskAndReplyWithResult(
438        blocking_task_runner_.get(),
439        FROM_HERE,
440        base::Bind(&util::ReadResourceIdFromGDocFile, local_src_file_path),
441        base::Bind(&CopyOperation::TransferFileForResourceId,
442                   weak_ptr_factory_.GetWeakPtr(),
443                   local_src_file_path,
444                   remote_dest_file_path,
445                   callback));
446  } else {
447    ScheduleTransferRegularFile(local_src_file_path, remote_dest_file_path,
448                                callback);
449  }
450}
451
452void CopyOperation::TransferFileForResourceId(
453    const base::FilePath& local_file_path,
454    const base::FilePath& remote_dest_file_path,
455    const FileOperationCallback& callback,
456    const std::string& resource_id) {
457  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
458  DCHECK(!callback.is_null());
459
460  if (resource_id.empty()) {
461    // If |resource_id| is empty, upload the local file as a regular file.
462    ScheduleTransferRegularFile(local_file_path, remote_dest_file_path,
463                                callback);
464    return;
465  }
466
467  // GDoc file may contain a resource ID in the old format.
468  const std::string canonicalized_resource_id =
469      drive_service_->CanonicalizeResourceId(resource_id);
470
471  // Otherwise, copy the document on the server side and add the new copy
472  // to the destination directory (collection).
473  CopyHostedDocumentToDirectory(
474      remote_dest_file_path.DirName(),
475      canonicalized_resource_id,
476      // Drop the document extension, which should not be
477      // in the document title.
478      // TODO(yoshiki): Remove this code with crbug.com/223304.
479      remote_dest_file_path.BaseName().RemoveExtension().value(),
480      callback);
481}
482
483}  // namespace file_system
484}  // namespace drive
485