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 namespace { 29 30 FileError PrepareCopy(internal::ResourceMetadata* metadata, 31 const base::FilePath& src_path, 32 const base::FilePath& dest_path, 33 ResourceEntry* src_entry, 34 std::string* parent_resource_id) { 35 FileError error = metadata->GetResourceEntryByPath(src_path, src_entry); 36 if (error != FILE_ERROR_OK) 37 return error; 38 39 ResourceEntry parent_entry; 40 error = metadata->GetResourceEntryByPath(dest_path.DirName(), &parent_entry); 41 if (error != FILE_ERROR_OK) 42 return error; 43 44 if (!parent_entry.file_info().is_directory()) 45 return FILE_ERROR_NOT_A_DIRECTORY; 46 47 // Drive File System doesn't support recursive copy. 48 if (src_entry->file_info().is_directory()) 49 return FILE_ERROR_NOT_A_FILE; 50 51 *parent_resource_id = parent_entry.resource_id(); 52 return FILE_ERROR_OK; 53 } 54 55 int64 GetFileSize(const base::FilePath& file_path) { 56 int64 file_size; 57 if (!base::GetFileSize(file_path, &file_size)) 58 return -1; 59 return file_size; 60 } 61 62 // Stores the copied entry and returns its path. 63 FileError UpdateLocalStateForServerSideCopy( 64 internal::ResourceMetadata* metadata, 65 scoped_ptr<google_apis::ResourceEntry> resource_entry, 66 base::FilePath* file_path) { 67 DCHECK(resource_entry); 68 69 ResourceEntry entry; 70 std::string parent_resource_id; 71 if (!ConvertToResourceEntry(*resource_entry, &entry, &parent_resource_id)) 72 return FILE_ERROR_NOT_A_FILE; 73 74 std::string parent_local_id; 75 FileError error = metadata->GetIdByResourceId(parent_resource_id, 76 &parent_local_id); 77 if (error != FILE_ERROR_OK) 78 return error; 79 entry.set_parent_local_id(parent_local_id); 80 81 std::string local_id; 82 error = metadata->AddEntry(entry, &local_id); 83 // Depending on timing, the metadata may have inserted via change list 84 // already. So, FILE_ERROR_EXISTS is not an error. 85 if (error == FILE_ERROR_EXISTS) 86 error = metadata->GetIdByResourceId(entry.resource_id(), &local_id); 87 88 if (error == FILE_ERROR_OK) 89 *file_path = metadata->GetFilePath(local_id); 90 91 return error; 92 } 93 94 // Stores the file at |local_file_path| to the cache as a content of entry at 95 // |remote_dest_path|, and marks it dirty. 96 FileError UpdateLocalStateForScheduleTransfer( 97 internal::ResourceMetadata* metadata, 98 internal::FileCache* cache, 99 const base::FilePath& local_src_path, 100 const base::FilePath& remote_dest_path, 101 std::string* local_id) { 102 FileError error = metadata->GetIdByPath(remote_dest_path, local_id); 103 if (error != FILE_ERROR_OK) 104 return error; 105 106 ResourceEntry entry; 107 error = metadata->GetResourceEntryById(*local_id, &entry); 108 if (error != FILE_ERROR_OK) 109 return error; 110 111 error = cache->Store( 112 *local_id, entry.file_specific_info().md5(), local_src_path, 113 internal::FileCache::FILE_OPERATION_COPY); 114 if (error != FILE_ERROR_OK) 115 return error; 116 117 error = cache->MarkDirty(*local_id); 118 if (error != FILE_ERROR_OK) 119 return error; 120 121 return FILE_ERROR_OK; 122 } 123 124 // Gets the file size of the |local_path|, and the ResourceEntry for the parent 125 // of |remote_path| to prepare the necessary information for transfer. 126 FileError PrepareTransferFileFromLocalToRemote( 127 internal::ResourceMetadata* metadata, 128 const base::FilePath& local_src_path, 129 const base::FilePath& remote_dest_path, 130 std::string* gdoc_resource_id, 131 std::string* parent_resource_id) { 132 ResourceEntry parent_entry; 133 FileError error = metadata->GetResourceEntryByPath( 134 remote_dest_path.DirName(), &parent_entry); 135 if (error != FILE_ERROR_OK) 136 return error; 137 138 // The destination's parent must be a directory. 139 if (!parent_entry.file_info().is_directory()) 140 return FILE_ERROR_NOT_A_DIRECTORY; 141 142 // Try to parse GDoc File and extract the resource id, if necessary. 143 // Failing isn't problem. It'd be handled as a regular file, then. 144 if (util::HasGDocFileExtension(local_src_path)) { 145 *gdoc_resource_id = util::ReadResourceIdFromGDocFile(local_src_path); 146 *parent_resource_id = parent_entry.resource_id(); 147 } 148 149 return FILE_ERROR_OK; 150 } 151 152 } // namespace 153 154 struct CopyOperation::CopyParams { 155 base::FilePath dest_file_path; 156 bool preserve_last_modified; 157 FileOperationCallback callback; 158 }; 159 160 CopyOperation::CopyOperation(base::SequencedTaskRunner* blocking_task_runner, 161 OperationObserver* observer, 162 JobScheduler* scheduler, 163 internal::ResourceMetadata* metadata, 164 internal::FileCache* cache, 165 const ResourceIdCanonicalizer& id_canonicalizer) 166 : blocking_task_runner_(blocking_task_runner), 167 observer_(observer), 168 scheduler_(scheduler), 169 metadata_(metadata), 170 cache_(cache), 171 id_canonicalizer_(id_canonicalizer), 172 create_file_operation_(new CreateFileOperation(blocking_task_runner, 173 observer, 174 scheduler, 175 metadata, 176 cache)), 177 weak_ptr_factory_(this) { 178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 179 } 180 181 CopyOperation::~CopyOperation() { 182 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 183 } 184 185 void CopyOperation::Copy(const base::FilePath& src_file_path, 186 const base::FilePath& dest_file_path, 187 bool preserve_last_modified, 188 const FileOperationCallback& callback) { 189 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 190 DCHECK(!callback.is_null()); 191 192 CopyParams params; 193 params.dest_file_path = dest_file_path; 194 params.preserve_last_modified = preserve_last_modified; 195 params.callback = callback; 196 197 ResourceEntry* src_entry = new ResourceEntry; 198 std::string* parent_resource_id = new std::string; 199 base::PostTaskAndReplyWithResult( 200 blocking_task_runner_.get(), 201 FROM_HERE, 202 base::Bind(&PrepareCopy, 203 metadata_, src_file_path, dest_file_path, 204 src_entry, parent_resource_id), 205 base::Bind(&CopyOperation::CopyAfterPrepare, 206 weak_ptr_factory_.GetWeakPtr(), params, 207 base::Owned(src_entry), base::Owned(parent_resource_id))); 208 } 209 210 void CopyOperation::CopyAfterPrepare(const CopyParams& params, 211 ResourceEntry* src_entry, 212 std::string* parent_resource_id, 213 FileError error) { 214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 215 DCHECK(!params.callback.is_null()); 216 217 if (error != FILE_ERROR_OK) { 218 params.callback.Run(error); 219 return; 220 } 221 222 base::FilePath new_title = params.dest_file_path.BaseName(); 223 if (src_entry->file_specific_info().is_hosted_document()) { 224 // Drop the document extension, which should not be in the title. 225 // TODO(yoshiki): Remove this code with crbug.com/223304. 226 new_title = new_title.RemoveExtension(); 227 } 228 229 base::Time last_modified = 230 params.preserve_last_modified ? 231 base::Time::FromInternalValue(src_entry->file_info().last_modified()) : 232 base::Time(); 233 234 CopyResourceOnServer( 235 src_entry->resource_id(), *parent_resource_id, 236 new_title.AsUTF8Unsafe(), last_modified, params.callback); 237 } 238 239 void CopyOperation::TransferFileFromLocalToRemote( 240 const base::FilePath& local_src_path, 241 const base::FilePath& remote_dest_path, 242 const FileOperationCallback& callback) { 243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 244 DCHECK(!callback.is_null()); 245 246 std::string* gdoc_resource_id = new std::string; 247 std::string* parent_resource_id = new std::string; 248 base::PostTaskAndReplyWithResult( 249 blocking_task_runner_.get(), 250 FROM_HERE, 251 base::Bind( 252 &PrepareTransferFileFromLocalToRemote, 253 metadata_, local_src_path, remote_dest_path, 254 gdoc_resource_id, parent_resource_id), 255 base::Bind( 256 &CopyOperation::TransferFileFromLocalToRemoteAfterPrepare, 257 weak_ptr_factory_.GetWeakPtr(), 258 local_src_path, remote_dest_path, callback, 259 base::Owned(gdoc_resource_id), base::Owned(parent_resource_id))); 260 } 261 262 void CopyOperation::TransferFileFromLocalToRemoteAfterPrepare( 263 const base::FilePath& local_src_path, 264 const base::FilePath& remote_dest_path, 265 const FileOperationCallback& callback, 266 std::string* gdoc_resource_id, 267 std::string* parent_resource_id, 268 FileError error) { 269 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 270 DCHECK(!callback.is_null()); 271 272 if (error != FILE_ERROR_OK) { 273 callback.Run(error); 274 return; 275 } 276 277 // For regular files, schedule the transfer. 278 if (gdoc_resource_id->empty()) { 279 ScheduleTransferRegularFile(local_src_path, remote_dest_path, callback); 280 return; 281 } 282 283 // This is uploading a JSON file representing a hosted document. 284 // Copy the document on the Drive server. 285 286 // GDoc file may contain a resource ID in the old format. 287 const std::string canonicalized_resource_id = 288 id_canonicalizer_.Run(*gdoc_resource_id); 289 290 CopyResourceOnServer( 291 canonicalized_resource_id, *parent_resource_id, 292 // Drop the document extension, which should not be in the title. 293 // TODO(yoshiki): Remove this code with crbug.com/223304. 294 remote_dest_path.BaseName().RemoveExtension().AsUTF8Unsafe(), 295 base::Time(), 296 callback); 297 } 298 299 void CopyOperation::CopyResourceOnServer( 300 const std::string& resource_id, 301 const std::string& parent_resource_id, 302 const std::string& new_title, 303 const base::Time& last_modified, 304 const FileOperationCallback& callback) { 305 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 306 DCHECK(!callback.is_null()); 307 308 scheduler_->CopyResource( 309 resource_id, parent_resource_id, new_title, last_modified, 310 base::Bind(&CopyOperation::CopyResourceOnServerAfterServerSideCopy, 311 weak_ptr_factory_.GetWeakPtr(), 312 callback)); 313 } 314 315 void CopyOperation::CopyResourceOnServerAfterServerSideCopy( 316 const FileOperationCallback& callback, 317 google_apis::GDataErrorCode status, 318 scoped_ptr<google_apis::ResourceEntry> resource_entry) { 319 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 320 DCHECK(!callback.is_null()); 321 322 FileError error = GDataToFileError(status); 323 if (error != FILE_ERROR_OK) { 324 callback.Run(error); 325 return; 326 } 327 328 // The copy on the server side is completed successfully. Update the local 329 // metadata. 330 base::FilePath* file_path = new base::FilePath; 331 base::PostTaskAndReplyWithResult( 332 blocking_task_runner_.get(), 333 FROM_HERE, 334 base::Bind(&UpdateLocalStateForServerSideCopy, 335 metadata_, base::Passed(&resource_entry), file_path), 336 base::Bind(&CopyOperation::CopyResourceOnServerAfterUpdateLocalState, 337 weak_ptr_factory_.GetWeakPtr(), 338 callback, base::Owned(file_path))); 339 } 340 341 void CopyOperation::CopyResourceOnServerAfterUpdateLocalState( 342 const FileOperationCallback& callback, 343 base::FilePath* file_path, 344 FileError error) { 345 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 346 DCHECK(!callback.is_null()); 347 348 if (error == FILE_ERROR_OK) 349 observer_->OnDirectoryChangedByOperation(file_path->DirName()); 350 callback.Run(error); 351 } 352 353 void CopyOperation::ScheduleTransferRegularFile( 354 const base::FilePath& local_src_path, 355 const base::FilePath& remote_dest_path, 356 const FileOperationCallback& callback) { 357 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 358 DCHECK(!callback.is_null()); 359 360 base::PostTaskAndReplyWithResult( 361 blocking_task_runner_.get(), 362 FROM_HERE, 363 base::Bind(&GetFileSize, local_src_path), 364 base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterGetFileSize, 365 weak_ptr_factory_.GetWeakPtr(), 366 local_src_path, remote_dest_path, callback)); 367 } 368 369 void CopyOperation::ScheduleTransferRegularFileAfterGetFileSize( 370 const base::FilePath& local_src_path, 371 const base::FilePath& remote_dest_path, 372 const FileOperationCallback& callback, 373 int64 local_file_size) { 374 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 375 DCHECK(!callback.is_null()); 376 377 if (local_file_size < 0) { 378 callback.Run(FILE_ERROR_NOT_FOUND); 379 return; 380 } 381 382 // For regular files, check the server-side quota whether sufficient space 383 // is available for the file to be uploaded. 384 scheduler_->GetAboutResource( 385 base::Bind( 386 &CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource, 387 weak_ptr_factory_.GetWeakPtr(), 388 local_src_path, remote_dest_path, callback, local_file_size)); 389 } 390 391 void CopyOperation::ScheduleTransferRegularFileAfterGetAboutResource( 392 const base::FilePath& local_src_path, 393 const base::FilePath& remote_dest_path, 394 const FileOperationCallback& callback, 395 int64 local_file_size, 396 google_apis::GDataErrorCode status, 397 scoped_ptr<google_apis::AboutResource> about_resource) { 398 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 399 DCHECK(!callback.is_null()); 400 401 FileError error = GDataToFileError(status); 402 if (error != FILE_ERROR_OK) { 403 callback.Run(error); 404 return; 405 } 406 407 DCHECK(about_resource); 408 const int64 space = 409 about_resource->quota_bytes_total() - about_resource->quota_bytes_used(); 410 if (space < local_file_size) { 411 callback.Run(FILE_ERROR_NO_SERVER_SPACE); 412 return; 413 } 414 415 create_file_operation_->CreateFile( 416 remote_dest_path, 417 true, // Exclusive (i.e. fail if a file already exists). 418 std::string(), // no specific mime type; CreateFile should guess it. 419 base::Bind(&CopyOperation::ScheduleTransferRegularFileAfterCreate, 420 weak_ptr_factory_.GetWeakPtr(), 421 local_src_path, remote_dest_path, callback)); 422 } 423 424 void CopyOperation::ScheduleTransferRegularFileAfterCreate( 425 const base::FilePath& local_src_path, 426 const base::FilePath& remote_dest_path, 427 const FileOperationCallback& callback, 428 FileError error) { 429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 430 DCHECK(!callback.is_null()); 431 432 if (error != FILE_ERROR_OK) { 433 callback.Run(error); 434 return; 435 } 436 437 std::string* local_id = new std::string; 438 base::PostTaskAndReplyWithResult( 439 blocking_task_runner_.get(), 440 FROM_HERE, 441 base::Bind( 442 &UpdateLocalStateForScheduleTransfer, 443 metadata_, cache_, local_src_path, remote_dest_path, local_id), 444 base::Bind( 445 &CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState, 446 weak_ptr_factory_.GetWeakPtr(), callback, base::Owned(local_id))); 447 } 448 449 void CopyOperation::ScheduleTransferRegularFileAfterUpdateLocalState( 450 const FileOperationCallback& callback, 451 std::string* local_id, 452 FileError error) { 453 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 454 DCHECK(!callback.is_null()); 455 456 if (error == FILE_ERROR_OK) 457 observer_->OnCacheFileUploadNeededByOperation(*local_id); 458 callback.Run(error); 459 } 460 461 } // namespace file_system 462 } // namespace drive 463