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