// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/sync_file_system/drive_backend/conflict_resolver.h" #include #include #include "base/callback.h" #include "base/format_macros.h" #include "base/location.h" #include "base/logging.h" #include "base/strings/stringprintf.h" #include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h" #include "chrome/browser/sync_file_system/drive_backend/metadata_database.h" #include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.h" #include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h" #include "chrome/browser/sync_file_system/drive_backend/sync_task_manager.h" #include "chrome/browser/sync_file_system/drive_backend/sync_task_token.h" #include "chrome/browser/sync_file_system/logger.h" #include "components/drive/drive_api_util.h" #include "components/drive/drive_uploader.h" #include "components/drive/service/drive_service_interface.h" #include "google_apis/drive/drive_api_parser.h" namespace sync_file_system { namespace drive_backend { ConflictResolver::ConflictResolver(SyncEngineContext* sync_context) : sync_context_(sync_context), weak_ptr_factory_(this) {} ConflictResolver::~ConflictResolver() {} void ConflictResolver::RunPreflight(scoped_ptr token) { token->InitializeTaskLog("Conflict Resolution"); scoped_ptr task_blocker(new TaskBlocker); task_blocker->exclusive = true; SyncTaskManager::UpdateTaskBlocker( std::move(token), std::move(task_blocker), base::Bind(&ConflictResolver::RunExclusive, weak_ptr_factory_.GetWeakPtr())); } void ConflictResolver::RunExclusive(scoped_ptr token) { if (!IsContextReady()) { SyncTaskManager::NotifyTaskDone(std::move(token), SYNC_STATUS_FAILED); return; } // Conflict resolution should be invoked on clean tree. if (metadata_database()->HasDirtyTracker()) { SyncTaskManager::NotifyTaskDone(std::move(token), SYNC_STATUS_RETRY); return; } TrackerIDSet trackers; if (metadata_database()->GetMultiParentFileTrackers( &target_file_id_, &trackers)) { DCHECK_LT(1u, trackers.size()); if (!trackers.has_active()) { NOTREACHED(); SyncTaskManager::NotifyTaskDone(std::move(token), SYNC_STATUS_FAILED); return; } token->RecordLog(base::StringPrintf( "Detected multi-parent trackers (active tracker_id=%" PRId64 ")", trackers.active_tracker())); DCHECK(trackers.has_active()); for (TrackerIDSet::const_iterator itr = trackers.begin(); itr != trackers.end(); ++itr) { FileTracker tracker; if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) { NOTREACHED(); continue; } if (tracker.active()) continue; FileTracker parent_tracker; bool should_success = metadata_database()->FindTrackerByTrackerID( tracker.parent_tracker_id(), &parent_tracker); if (!should_success) { NOTREACHED(); SyncTaskManager::NotifyTaskDone(std::move(token), SYNC_STATUS_FAILED); return; } parents_to_remove_.push_back(parent_tracker.file_id()); } DetachFromNonPrimaryParents(std::move(token)); return; } if (metadata_database()->GetConflictingTrackers(&trackers)) { target_file_id_ = PickPrimaryFile(trackers); DCHECK(!target_file_id_.empty()); int64_t primary_tracker_id = -1; for (TrackerIDSet::const_iterator itr = trackers.begin(); itr != trackers.end(); ++itr) { FileTracker tracker; if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) { NOTREACHED(); continue; } if (tracker.file_id() != target_file_id_) { non_primary_file_ids_.push_back( std::make_pair(tracker.file_id(), tracker.synced_details().etag())); } else { primary_tracker_id = tracker.tracker_id(); } } token->RecordLog(base::StringPrintf( "Detected %" PRIuS " conflicting trackers " "(primary tracker_id=%" PRId64 ")", non_primary_file_ids_.size(), primary_tracker_id)); RemoveNonPrimaryFiles(std::move(token)); return; } SyncTaskManager::NotifyTaskDone(std::move(token), SYNC_STATUS_NO_CONFLICT); } void ConflictResolver::DetachFromNonPrimaryParents( scoped_ptr token) { DCHECK(!parents_to_remove_.empty()); // TODO(tzik): Check if ETag match is available for // RemoteResourceFromDirectory. std::string parent_folder_id = parents_to_remove_.back(); parents_to_remove_.pop_back(); token->RecordLog(base::StringPrintf( "Detach %s from %s", target_file_id_.c_str(), parent_folder_id.c_str())); drive_service()->RemoveResourceFromDirectory( parent_folder_id, target_file_id_, base::Bind(&ConflictResolver::DidDetachFromParent, weak_ptr_factory_.GetWeakPtr(), base::Passed(&token))); } void ConflictResolver::DidDetachFromParent( scoped_ptr token, google_apis::DriveApiErrorCode error) { SyncStatusCode status = DriveApiErrorCodeToSyncStatusCode(error); if (status != SYNC_STATUS_OK) { SyncTaskManager::NotifyTaskDone(std::move(token), status); return; } if (!parents_to_remove_.empty()) { DetachFromNonPrimaryParents(std::move(token)); return; } SyncTaskManager::NotifyTaskDone(std::move(token), SYNC_STATUS_OK); } std::string ConflictResolver::PickPrimaryFile(const TrackerIDSet& trackers) { scoped_ptr primary; for (TrackerIDSet::const_iterator itr = trackers.begin(); itr != trackers.end(); ++itr) { FileTracker tracker; if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) { NOTREACHED(); continue; } scoped_ptr file_metadata(new FileMetadata); if (!metadata_database()->FindFileByFileID( tracker.file_id(), file_metadata.get())) { NOTREACHED(); continue; } if (!primary) { primary = std::move(file_metadata); continue; } DCHECK(primary->details().file_kind() == FILE_KIND_FILE || primary->details().file_kind() == FILE_KIND_FOLDER); DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE || file_metadata->details().file_kind() == FILE_KIND_FOLDER); if (primary->details().file_kind() == FILE_KIND_FILE) { if (file_metadata->details().file_kind() == FILE_KIND_FOLDER) { // Prioritize folders over regular files. primary = std::move(file_metadata); continue; } DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE); if (primary->details().modification_time() < file_metadata->details().modification_time()) { // Prioritize last write for regular files. primary = std::move(file_metadata); continue; } continue; } DCHECK(primary->details().file_kind() == FILE_KIND_FOLDER); if (file_metadata->details().file_kind() == FILE_KIND_FILE) { // Prioritize folders over regular files. continue; } DCHECK(file_metadata->details().file_kind() == FILE_KIND_FOLDER); if (primary->details().creation_time() > file_metadata->details().creation_time()) { // Prioritize first create for folders. primary = std::move(file_metadata); continue; } } if (primary) return primary->file_id(); return std::string(); } void ConflictResolver::RemoveNonPrimaryFiles(scoped_ptr token) { DCHECK(!non_primary_file_ids_.empty()); std::string file_id = non_primary_file_ids_.back().first; std::string etag = non_primary_file_ids_.back().second; non_primary_file_ids_.pop_back(); DCHECK_NE(target_file_id_, file_id); token->RecordLog(base::StringPrintf( "Remove non-primary file %s", file_id.c_str())); // TODO(tzik): Check if the file is a folder, and merge its contents into // the folder identified by |target_file_id_|. drive_service()->DeleteResource( file_id, etag, base::Bind(&ConflictResolver::DidRemoveFile, weak_ptr_factory_.GetWeakPtr(), base::Passed(&token), file_id)); } void ConflictResolver::DidRemoveFile(scoped_ptr token, const std::string& file_id, google_apis::DriveApiErrorCode error) { if (error == google_apis::HTTP_PRECONDITION || error == google_apis::HTTP_CONFLICT) { UpdateFileMetadata(file_id, std::move(token)); return; } SyncStatusCode status = DriveApiErrorCodeToSyncStatusCode(error); if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) { SyncTaskManager::NotifyTaskDone(std::move(token), status); return; } deleted_file_ids_.push_back(file_id); if (!non_primary_file_ids_.empty()) { RemoveNonPrimaryFiles(std::move(token)); return; } status = metadata_database()->UpdateByDeletedRemoteFileList( deleted_file_ids_); SyncTaskManager::NotifyTaskDone(std::move(token), status); } bool ConflictResolver::IsContextReady() { return sync_context_->GetDriveService() && sync_context_->GetMetadataDatabase(); } void ConflictResolver::UpdateFileMetadata( const std::string& file_id, scoped_ptr token) { drive_service()->GetFileResource( file_id, base::Bind(&ConflictResolver::DidGetRemoteMetadata, weak_ptr_factory_.GetWeakPtr(), file_id, base::Passed(&token))); } void ConflictResolver::DidGetRemoteMetadata( const std::string& file_id, scoped_ptr token, google_apis::DriveApiErrorCode error, scoped_ptr entry) { SyncStatusCode status = DriveApiErrorCodeToSyncStatusCode(error); if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) { SyncTaskManager::NotifyTaskDone(std::move(token), status); return; } if (error != google_apis::HTTP_NOT_FOUND) { status = metadata_database()->UpdateByDeletedRemoteFile(file_id); SyncTaskManager::NotifyTaskDone(std::move(token), status); return; } if (!entry) { NOTREACHED(); SyncTaskManager::NotifyTaskDone(std::move(token), SYNC_STATUS_FAILED); return; } status = metadata_database()->UpdateByFileResource(*entry); SyncTaskManager::NotifyTaskDone(std::move(token), status); } drive::DriveServiceInterface* ConflictResolver::drive_service() { set_used_network(true); return sync_context_->GetDriveService(); } MetadataDatabase* ConflictResolver::metadata_database() { return sync_context_->GetMetadataDatabase(); } } // namespace drive_backend } // namespace sync_file_system