local_to_remote_syncer.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
1// Copyright 2013 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/sync_file_system/drive_backend/local_to_remote_syncer.h" 6 7#include <string> 8#include <vector> 9 10#include "base/callback.h" 11#include "base/location.h" 12#include "base/logging.h" 13#include "base/sequenced_task_runner.h" 14#include "base/task_runner_util.h" 15#include "chrome/browser/drive/drive_api_util.h" 16#include "chrome/browser/drive/drive_service_interface.h" 17#include "chrome/browser/drive/drive_uploader.h" 18#include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h" 19#include "chrome/browser/sync_file_system/drive_backend/folder_creator.h" 20#include "chrome/browser/sync_file_system/drive_backend/metadata_database.h" 21#include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.h" 22#include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h" 23#include "chrome/browser/sync_file_system/drive_backend_v1/drive_file_sync_util.h" 24#include "google_apis/drive/drive_api_parser.h" 25#include "webkit/common/fileapi/file_system_util.h" 26 27namespace sync_file_system { 28namespace drive_backend { 29 30namespace { 31 32scoped_ptr<FileTracker> FindTrackerByID(MetadataDatabase* metadata_database, 33 int64 tracker_id) { 34 scoped_ptr<FileTracker> tracker(new FileTracker); 35 if (metadata_database->FindTrackerByTrackerID(tracker_id, tracker.get())) 36 return tracker.Pass(); 37 return scoped_ptr<FileTracker>(); 38} 39 40void ReturnRetryOnSuccess(const SyncStatusCallback& callback, 41 SyncStatusCode status) { 42 if (status == SYNC_STATUS_OK) 43 status = SYNC_STATUS_RETRY; 44 callback.Run(status); 45} 46 47} // namespace 48 49LocalToRemoteSyncer::LocalToRemoteSyncer(SyncEngineContext* sync_context, 50 const SyncFileMetadata& local_metadata, 51 const FileChange& local_change, 52 const base::FilePath& local_path, 53 const fileapi::FileSystemURL& url) 54 : sync_context_(sync_context), 55 local_metadata_(local_metadata), 56 local_change_(local_change), 57 local_path_(local_path), 58 url_(url), 59 sync_action_(SYNC_ACTION_NONE), 60 weak_ptr_factory_(this) { 61} 62 63LocalToRemoteSyncer::~LocalToRemoteSyncer() { 64} 65 66void LocalToRemoteSyncer::Run(const SyncStatusCallback& callback) { 67 if (!IsContextReady()) { 68 NOTREACHED(); 69 callback.Run(SYNC_STATUS_FAILED); 70 return; 71 } 72 73 SyncStatusCallback wrapped_callback = base::Bind( 74 &LocalToRemoteSyncer::SyncCompleted, weak_ptr_factory_.GetWeakPtr(), 75 callback); 76 77 std::string app_id = url_.origin().host(); 78 base::FilePath path = url_.path(); 79 80 scoped_ptr<FileTracker> active_ancestor_tracker(new FileTracker); 81 base::FilePath active_ancestor_path; 82 if (!metadata_database()->FindNearestActiveAncestor( 83 app_id, path, 84 active_ancestor_tracker.get(), &active_ancestor_path)) { 85 // The app is disabled or not registered. 86 callback.Run(SYNC_STATUS_UNKNOWN_ORIGIN); 87 return; 88 } 89 DCHECK(active_ancestor_tracker->active()); 90 DCHECK(active_ancestor_tracker->has_synced_details()); 91 const FileDetails& active_ancestor_details = 92 active_ancestor_tracker->synced_details(); 93 94 // TODO(tzik): Consider handling 95 // active_ancestor_tracker->synced_details().missing() case. 96 97 DCHECK(active_ancestor_details.file_kind() == FILE_KIND_FILE || 98 active_ancestor_details.file_kind() == FILE_KIND_FOLDER); 99 100 base::FilePath missing_entries; 101 if (active_ancestor_path.empty()) { 102 missing_entries = path; 103 } else if (active_ancestor_path != path) { 104 bool should_success = active_ancestor_path.AppendRelativePath( 105 path, &missing_entries); 106 if (!should_success) { 107 NOTREACHED(); 108 callback.Run(SYNC_STATUS_FAILED); 109 return; 110 } 111 } 112 113 std::vector<base::FilePath::StringType> missing_components; 114 fileapi::VirtualPath::GetComponents(missing_entries, &missing_components); 115 116 if (!missing_components.empty()) { 117 if (local_change_.IsDelete() || 118 local_metadata_.file_type == SYNC_FILE_TYPE_UNKNOWN) { 119 // !IsDelete() but SYNC_FILE_TYPE_UNKNOWN could happen when a file is 120 // deleted by recursive deletion (which is not recorded by tracker) 121 // but there're remaining changes for the same file in the tracker. 122 123 // Local file is deleted and remote file is missing, already deleted or 124 // not yet synced. There is nothing to do for the file. 125 callback.Run(SYNC_STATUS_OK); 126 return; 127 } 128 } 129 130 if (missing_components.size() > 1) { 131 // The original target doesn't have remote file and parent. 132 // Try creating the parent first. 133 if (active_ancestor_details.file_kind() == FILE_KIND_FOLDER) { 134 remote_parent_folder_tracker_ = active_ancestor_tracker.Pass(); 135 target_path_ = active_ancestor_path.Append(missing_components[0]); 136 CreateRemoteFolder(wrapped_callback); 137 return; 138 } 139 140 DCHECK(active_ancestor_details.file_kind() == FILE_KIND_FILE); 141 remote_parent_folder_tracker_ = 142 FindTrackerByID(metadata_database(), 143 active_ancestor_tracker->parent_tracker_id()); 144 remote_file_tracker_ = active_ancestor_tracker.Pass(); 145 target_path_ = active_ancestor_path; 146 DeleteRemoteFile(base::Bind(&LocalToRemoteSyncer::DidDeleteForCreateFolder, 147 weak_ptr_factory_.GetWeakPtr(), 148 wrapped_callback)); 149 150 return; 151 } 152 153 if (missing_components.empty()) { 154 // The original target has remote active file/folder. 155 remote_parent_folder_tracker_ = 156 FindTrackerByID(metadata_database(), 157 active_ancestor_tracker->parent_tracker_id()); 158 remote_file_tracker_ = active_ancestor_tracker.Pass(); 159 target_path_ = url_.path(); 160 DCHECK(target_path_ == active_ancestor_path); 161 162 if (remote_file_tracker_->dirty()) { 163 // Both local and remote file has pending modification. 164 HandleConflict(wrapped_callback); 165 return; 166 } 167 168 // Non-conflicting file/folder update case. 169 HandleExistingRemoteFile(wrapped_callback); 170 return; 171 } 172 173 DCHECK(local_change_.IsAddOrUpdate()); 174 DCHECK_EQ(1u, missing_components.size()); 175 // The original target has remote parent folder and doesn't have remote active 176 // file. 177 // Upload the file as a new file or create a folder. 178 remote_parent_folder_tracker_ = active_ancestor_tracker.Pass(); 179 target_path_ = url_.path(); 180 DCHECK(target_path_ == active_ancestor_path.Append(missing_components[0])); 181 if (local_change_.file_type() == SYNC_FILE_TYPE_FILE) { 182 UploadNewFile(wrapped_callback); 183 return; 184 } 185 CreateRemoteFolder(wrapped_callback); 186} 187 188void LocalToRemoteSyncer::SyncCompleted(const SyncStatusCallback& callback, 189 SyncStatusCode status) { 190 if (status == SYNC_STATUS_OK && target_path_ != url_.path()) { 191 callback.Run(SYNC_STATUS_RETRY); 192 return; 193 } 194 195 callback.Run(status); 196} 197 198void LocalToRemoteSyncer::HandleConflict(const SyncStatusCallback& callback) { 199 DCHECK(remote_file_tracker_); 200 DCHECK(remote_file_tracker_->has_synced_details()); 201 DCHECK(remote_file_tracker_->active()); 202 DCHECK(remote_file_tracker_->dirty()); 203 204 if (local_change_.IsFile()) { 205 UploadNewFile(callback); 206 return; 207 } 208 209 DCHECK(local_change_.IsDirectory()); 210 // Check if we can reuse the remote folder. 211 FileMetadata remote_file_metadata; 212 bool should_success = metadata_database()->FindFileByFileID( 213 remote_file_tracker_->file_id(), &remote_file_metadata); 214 if (!should_success) { 215 NOTREACHED(); 216 CreateRemoteFolder(callback); 217 return; 218 } 219 220 const FileDetails& remote_details = remote_file_metadata.details(); 221 base::FilePath title = fileapi::VirtualPath::BaseName(target_path_); 222 if (!remote_details.missing() && 223 remote_details.file_kind() == FILE_KIND_FOLDER && 224 remote_details.title() == title.AsUTF8Unsafe() && 225 HasFileAsParent(remote_details, 226 remote_parent_folder_tracker_->file_id())) { 227 metadata_database()->UpdateTracker( 228 remote_file_tracker_->tracker_id(), remote_details, callback); 229 return; 230 } 231 232 // Create new remote folder. 233 CreateRemoteFolder(callback); 234} 235 236void LocalToRemoteSyncer::HandleExistingRemoteFile( 237 const SyncStatusCallback& callback) { 238 DCHECK(remote_file_tracker_); 239 DCHECK(!remote_file_tracker_->dirty()); 240 DCHECK(remote_file_tracker_->active()); 241 DCHECK(remote_file_tracker_->has_synced_details()); 242 243 if (local_change_.IsDelete() || 244 local_metadata_.file_type == SYNC_FILE_TYPE_UNKNOWN) { 245 // Local file deletion for existing remote file. 246 DeleteRemoteFile(callback); 247 return; 248 } 249 250 DCHECK(local_change_.IsAddOrUpdate()); 251 DCHECK(local_change_.file_type() == SYNC_FILE_TYPE_FILE || 252 local_change_.file_type() == SYNC_FILE_TYPE_DIRECTORY); 253 254 const FileDetails& synced_details = remote_file_tracker_->synced_details(); 255 DCHECK(synced_details.file_kind() == FILE_KIND_FILE || 256 synced_details.file_kind() == FILE_KIND_FOLDER); 257 if (local_change_.file_type() == SYNC_FILE_TYPE_FILE) { 258 if (synced_details.file_kind() == FILE_KIND_FILE) { 259 // Non-conflicting local file update to existing remote regular file. 260 UploadExistingFile(callback); 261 return; 262 } 263 264 DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind()); 265 // Non-conflicting local file update to existing remote *folder*. 266 // Assuming this case as local folder deletion + local file creation, delete 267 // the remote folder and upload the file. 268 DeleteRemoteFile(base::Bind(&LocalToRemoteSyncer::DidDeleteForUploadNewFile, 269 weak_ptr_factory_.GetWeakPtr(), 270 callback)); 271 return; 272 } 273 274 DCHECK_EQ(SYNC_FILE_TYPE_DIRECTORY, local_change_.file_type()); 275 if (synced_details.file_kind() == FILE_KIND_FILE) { 276 // Non-conflicting local folder creation to existing remote *file*. 277 // Assuming this case as local file deletion + local folder creation, delete 278 // the remote file and create a remote folder. 279 DeleteRemoteFile(base::Bind(&LocalToRemoteSyncer::DidDeleteForCreateFolder, 280 weak_ptr_factory_.GetWeakPtr(), callback)); 281 return; 282 } 283 284 // Non-conflicting local folder creation to existing remote folder. 285 DCHECK_EQ(FILE_KIND_FOLDER, synced_details.file_kind()); 286 callback.Run(SYNC_STATUS_OK); 287} 288 289void LocalToRemoteSyncer::DeleteRemoteFile( 290 const SyncStatusCallback& callback) { 291 DCHECK(remote_file_tracker_); 292 DCHECK(remote_file_tracker_->has_synced_details()); 293 294 sync_action_ = SYNC_ACTION_DELETED; 295 drive_service()->DeleteResource( 296 remote_file_tracker_->file_id(), 297 remote_file_tracker_->synced_details().etag(), 298 base::Bind(&LocalToRemoteSyncer::DidDeleteRemoteFile, 299 weak_ptr_factory_.GetWeakPtr(), 300 callback)); 301} 302 303void LocalToRemoteSyncer::DidDeleteRemoteFile( 304 const SyncStatusCallback& callback, 305 google_apis::GDataErrorCode error) { 306 if (error != google_apis::HTTP_SUCCESS && 307 error != google_apis::HTTP_NOT_FOUND && 308 error != google_apis::HTTP_PRECONDITION && 309 error != google_apis::HTTP_CONFLICT) { 310 callback.Run(GDataErrorCodeToSyncStatusCode(error)); 311 return; 312 } 313 314 // Handle NOT_FOUND case as SUCCESS case. 315 // For PRECONDITION / CONFLICT case, the remote file is modified since the 316 // last sync completed. As our policy for deletion-modification conflict 317 // resolution, ignore the local deletion. 318 callback.Run(SYNC_STATUS_OK); 319} 320 321void LocalToRemoteSyncer::UploadExistingFile( 322 const SyncStatusCallback& callback) { 323 DCHECK(remote_file_tracker_); 324 DCHECK(remote_file_tracker_->has_synced_details()); 325 326 base::PostTaskAndReplyWithResult( 327 sync_context_->GetBlockingTaskRunner(), FROM_HERE, 328 base::Bind(&drive::util::GetMd5Digest, local_path_), 329 base::Bind(&LocalToRemoteSyncer::DidGetMD5ForUpload, 330 weak_ptr_factory_.GetWeakPtr(), 331 callback)); 332} 333 334void LocalToRemoteSyncer::DidGetMD5ForUpload( 335 const SyncStatusCallback& callback, 336 const std::string& local_file_md5) { 337 if (local_file_md5 == remote_file_tracker_->synced_details().md5()) { 338 // Local file is not changed. 339 callback.Run(SYNC_STATUS_OK); 340 return; 341 } 342 343 sync_action_ = SYNC_ACTION_UPDATED; 344 drive_uploader()->UploadExistingFile( 345 remote_file_tracker_->file_id(), 346 local_path_, 347 "application/octet_stream", 348 remote_file_tracker_->synced_details().etag(), 349 base::Bind(&LocalToRemoteSyncer::DidUploadExistingFile, 350 weak_ptr_factory_.GetWeakPtr(), 351 callback), 352 google_apis::ProgressCallback()); 353} 354 355void LocalToRemoteSyncer::DidUploadExistingFile( 356 const SyncStatusCallback& callback, 357 google_apis::GDataErrorCode error, 358 const GURL&, 359 scoped_ptr<google_apis::ResourceEntry> entry) { 360 if (error == google_apis::HTTP_PRECONDITION || 361 error == google_apis::HTTP_CONFLICT) { 362 // The remote file has unfetched remote change. Fetch latest metadata and 363 // update database with it. 364 // TODO(tzik): Consider adding local side low-priority dirtiness handling to 365 // handle this as ListChangesTask. 366 UpdateRemoteMetadata(remote_file_tracker_->file_id(), 367 base::Bind(&ReturnRetryOnSuccess, callback)); 368 return; 369 } 370 371 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); 372 if (status != SYNC_STATUS_OK) { 373 callback.Run(status); 374 return; 375 } 376 377 if (!entry) { 378 NOTREACHED(); 379 callback.Run(SYNC_STATUS_FAILED); 380 return; 381 } 382 383 DCHECK(entry); 384 metadata_database()->UpdateByFileResource( 385 *drive::util::ConvertResourceEntryToFileResource(*entry), 386 base::Bind(&LocalToRemoteSyncer::DidUpdateDatabaseForUploadExistingFile, 387 weak_ptr_factory_.GetWeakPtr(), 388 callback)); 389} 390 391void LocalToRemoteSyncer::DidUpdateDatabaseForUploadExistingFile( 392 const SyncStatusCallback& callback, 393 SyncStatusCode status) { 394 if (status != SYNC_STATUS_OK) { 395 callback.Run(status); 396 return; 397 } 398 399 FileMetadata file; 400 bool should_success = metadata_database()->FindFileByFileID( 401 remote_file_tracker_->file_id(), &file); 402 if (!should_success) { 403 NOTREACHED(); 404 callback.Run(SYNC_STATUS_FAILED); 405 return; 406 } 407 408 const FileDetails& details = file.details(); 409 base::FilePath title = fileapi::VirtualPath::BaseName(target_path_); 410 if (!details.missing() && 411 details.file_kind() == FILE_KIND_FILE && 412 details.title() == title.AsUTF8Unsafe() && 413 HasFileAsParent(details, 414 remote_parent_folder_tracker_->file_id())) { 415 metadata_database()->UpdateTracker( 416 remote_file_tracker_->tracker_id(), 417 file.details(), 418 callback); 419 return; 420 } 421 422 callback.Run(SYNC_STATUS_RETRY); 423} 424 425void LocalToRemoteSyncer::UpdateRemoteMetadata( 426 const std::string& file_id, 427 const SyncStatusCallback& callback) { 428 DCHECK(remote_file_tracker_); 429 drive_service()->GetResourceEntry( 430 file_id, 431 base::Bind(&LocalToRemoteSyncer::DidGetRemoteMetadata, 432 weak_ptr_factory_.GetWeakPtr(), 433 file_id, callback)); 434} 435 436void LocalToRemoteSyncer::DidGetRemoteMetadata( 437 const std::string& file_id, 438 const SyncStatusCallback& callback, 439 google_apis::GDataErrorCode error, 440 scoped_ptr<google_apis::ResourceEntry> entry) { 441 if (error == google_apis::HTTP_NOT_FOUND) { 442 metadata_database()->UpdateByDeletedRemoteFile(file_id, callback); 443 return; 444 } 445 446 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); 447 if (status != SYNC_STATUS_OK) { 448 callback.Run(status); 449 return; 450 } 451 452 if (!entry) { 453 NOTREACHED(); 454 callback.Run(SYNC_STATUS_FAILED); 455 return; 456 } 457 458 metadata_database()->UpdateByFileResource( 459 *drive::util::ConvertResourceEntryToFileResource(*entry), callback); 460} 461 462void LocalToRemoteSyncer::DidDeleteForUploadNewFile( 463 const SyncStatusCallback& callback, 464 SyncStatusCode status) { 465 if (status == SYNC_STATUS_HAS_CONFLICT) { 466 UpdateRemoteMetadata(remote_file_tracker_->file_id(), 467 base::Bind(&ReturnRetryOnSuccess, callback)); 468 return; 469 } 470 471 if (status != SYNC_STATUS_OK) { 472 callback.Run(status); 473 return; 474 } 475 476 UploadNewFile(callback); 477} 478 479void LocalToRemoteSyncer::DidDeleteForCreateFolder( 480 const SyncStatusCallback& callback, 481 SyncStatusCode status) { 482 if (status == SYNC_STATUS_HAS_CONFLICT) { 483 UpdateRemoteMetadata(remote_file_tracker_->file_id(), 484 base::Bind(&ReturnRetryOnSuccess, callback)); 485 return; 486 } 487 488 if (status != SYNC_STATUS_OK) { 489 callback.Run(status); 490 return; 491 } 492 493 CreateRemoteFolder(callback); 494} 495 496void LocalToRemoteSyncer::UploadNewFile(const SyncStatusCallback& callback) { 497 DCHECK(remote_parent_folder_tracker_); 498 499 sync_action_ = SYNC_ACTION_ADDED; 500 base::FilePath title = fileapi::VirtualPath::BaseName(target_path_); 501 drive_uploader()->UploadNewFile( 502 remote_parent_folder_tracker_->file_id(), 503 local_path_, 504 title.AsUTF8Unsafe(), 505 GetMimeTypeFromTitle(title), 506 base::Bind(&LocalToRemoteSyncer::DidUploadNewFile, 507 weak_ptr_factory_.GetWeakPtr(), 508 callback), 509 google_apis::ProgressCallback()); 510} 511 512void LocalToRemoteSyncer::DidUploadNewFile( 513 const SyncStatusCallback& callback, 514 google_apis::GDataErrorCode error, 515 const GURL& upload_location, 516 scoped_ptr<google_apis::ResourceEntry> entry) { 517 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); 518 if (status != SYNC_STATUS_OK) { 519 callback.Run(status); 520 return; 521 } 522 523 if (!entry) { 524 NOTREACHED(); 525 callback.Run(SYNC_STATUS_FAILED); 526 return; 527 } 528 529 metadata_database()->ReplaceActiveTrackerWithNewResource( 530 remote_parent_folder_tracker_->tracker_id(), 531 *drive::util::ConvertResourceEntryToFileResource(*entry), 532 callback); 533} 534 535void LocalToRemoteSyncer::CreateRemoteFolder( 536 const SyncStatusCallback& callback) { 537 DCHECK(remote_parent_folder_tracker_); 538 539 base::FilePath title = fileapi::VirtualPath::BaseName(target_path_); 540 sync_action_ = SYNC_ACTION_ADDED; 541 542 DCHECK(!folder_creator_); 543 folder_creator_.reset(new FolderCreator( 544 drive_service(), metadata_database(), 545 remote_parent_folder_tracker_->file_id(), 546 title.AsUTF8Unsafe())); 547 folder_creator_->Run(base::Bind( 548 &LocalToRemoteSyncer::DidCreateRemoteFolder, 549 weak_ptr_factory_.GetWeakPtr(), 550 callback)); 551} 552 553void LocalToRemoteSyncer::DidCreateRemoteFolder( 554 const SyncStatusCallback& callback, 555 const std::string& file_id, 556 SyncStatusCode status) { 557 scoped_ptr<FolderCreator> deleter = folder_creator_.Pass(); 558 if (status != SYNC_STATUS_OK) { 559 callback.Run(status); 560 return; 561 } 562 563 if (metadata_database()->TryNoSideEffectActivation( 564 remote_parent_folder_tracker_->tracker_id(), 565 file_id, callback)) { 566 // |callback| will be invoked by MetadataDatabase in this case. 567 return; 568 } 569 570 drive_service()->RemoveResourceFromDirectory( 571 remote_parent_folder_tracker_->file_id(), file_id, 572 base::Bind(&LocalToRemoteSyncer::DidDetachResourceForCreationConflict, 573 weak_ptr_factory_.GetWeakPtr(), 574 callback)); 575} 576 577void LocalToRemoteSyncer::DidDetachResourceForCreationConflict( 578 const SyncStatusCallback& callback, 579 google_apis::GDataErrorCode error) { 580 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); 581 if (status != SYNC_STATUS_OK) { 582 callback.Run(status); 583 return; 584 } 585 586 callback.Run(SYNC_STATUS_RETRY); 587} 588 589bool LocalToRemoteSyncer::IsContextReady() { 590 return sync_context_->GetDriveService() && 591 sync_context_->GetDriveUploader() && 592 sync_context_->GetMetadataDatabase(); 593} 594 595drive::DriveServiceInterface* LocalToRemoteSyncer::drive_service() { 596 set_used_network(true); 597 return sync_context_->GetDriveService(); 598} 599 600drive::DriveUploaderInterface* LocalToRemoteSyncer::drive_uploader() { 601 set_used_network(true); 602 return sync_context_->GetDriveUploader(); 603} 604 605MetadataDatabase* LocalToRemoteSyncer::metadata_database() { 606 return sync_context_->GetMetadataDatabase(); 607} 608 609} // namespace drive_backend 610} // namespace sync_file_system 611