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