conflict_resolver.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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/conflict_resolver.h" 6 7#include "base/callback.h" 8#include "base/format_macros.h" 9#include "base/location.h" 10#include "base/logging.h" 11#include "base/strings/stringprintf.h" 12#include "chrome/browser/drive/drive_api_util.h" 13#include "chrome/browser/drive/drive_service_interface.h" 14#include "chrome/browser/drive/drive_uploader.h" 15#include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h" 16#include "chrome/browser/sync_file_system/drive_backend/metadata_database.h" 17#include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.h" 18#include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h" 19#include "chrome/browser/sync_file_system/drive_backend/sync_task_manager.h" 20#include "chrome/browser/sync_file_system/drive_backend/sync_task_token.h" 21#include "chrome/browser/sync_file_system/logger.h" 22#include "google_apis/drive/drive_api_parser.h" 23 24namespace sync_file_system { 25namespace drive_backend { 26 27ConflictResolver::ConflictResolver(SyncEngineContext* sync_context) 28 : sync_context_(sync_context), 29 weak_ptr_factory_(this) {} 30 31ConflictResolver::~ConflictResolver() {} 32 33void ConflictResolver::RunPreflight(scoped_ptr<SyncTaskToken> token) { 34 token->InitializeTaskLog("Conflict Resolution"); 35 36 scoped_ptr<BlockingFactor> blocking_factor(new BlockingFactor); 37 blocking_factor->exclusive = true; 38 SyncTaskManager::UpdateBlockingFactor( 39 token.Pass(), blocking_factor.Pass(), 40 base::Bind(&ConflictResolver::RunExclusive, 41 weak_ptr_factory_.GetWeakPtr())); 42} 43 44void ConflictResolver::RunExclusive(scoped_ptr<SyncTaskToken> token) { 45 if (!IsContextReady()) { 46 NOTREACHED(); 47 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED); 48 return; 49 } 50 51 // Conflict resolution should be invoked on clean tree. 52 if (metadata_database()->HasDirtyTracker()) { 53 NOTREACHED(); 54 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED); 55 return; 56 } 57 58 TrackerIDSet trackers; 59 if (metadata_database()->GetMultiParentFileTrackers( 60 &target_file_id_, &trackers)) { 61 DCHECK_LT(1u, trackers.size()); 62 if (!trackers.has_active()) { 63 NOTREACHED(); 64 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED); 65 return; 66 } 67 68 token->RecordLog(base::StringPrintf( 69 "Detected multi-parent trackers (active tracker_id=%" PRId64 ")", 70 trackers.active_tracker())); 71 72 DCHECK(trackers.has_active()); 73 for (TrackerIDSet::const_iterator itr = trackers.begin(); 74 itr != trackers.end(); ++itr) { 75 FileTracker tracker; 76 if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) { 77 NOTREACHED(); 78 continue; 79 } 80 81 if (tracker.active()) 82 continue; 83 84 FileTracker parent_tracker; 85 bool should_success = metadata_database()->FindTrackerByTrackerID( 86 tracker.parent_tracker_id(), &parent_tracker); 87 if (!should_success) { 88 NOTREACHED(); 89 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED); 90 return; 91 } 92 parents_to_remove_.push_back(parent_tracker.file_id()); 93 } 94 DetachFromNonPrimaryParents(token.Pass()); 95 return; 96 } 97 98 if (metadata_database()->GetConflictingTrackers(&trackers)) { 99 target_file_id_ = PickPrimaryFile(trackers); 100 DCHECK(!target_file_id_.empty()); 101 int64 primary_tracker_id = -1; 102 for (TrackerIDSet::const_iterator itr = trackers.begin(); 103 itr != trackers.end(); ++itr) { 104 FileTracker tracker; 105 if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) { 106 NOTREACHED(); 107 continue; 108 } 109 if (tracker.file_id() != target_file_id_) { 110 non_primary_file_ids_.push_back( 111 std::make_pair(tracker.file_id(), tracker.synced_details().etag())); 112 } else { 113 primary_tracker_id = tracker.tracker_id(); 114 } 115 } 116 117 token->RecordLog(base::StringPrintf( 118 "Detected %" PRIuS " conflicting trackers " 119 "(primary tracker_id=%" PRId64 ")", 120 non_primary_file_ids_.size(), primary_tracker_id)); 121 122 RemoveNonPrimaryFiles(token.Pass()); 123 return; 124 } 125 126 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_NO_CONFLICT); 127} 128 129void ConflictResolver::DetachFromNonPrimaryParents( 130 scoped_ptr<SyncTaskToken> token) { 131 DCHECK(!parents_to_remove_.empty()); 132 133 // TODO(tzik): Check if ETag match is available for 134 // RemoteResourceFromDirectory. 135 std::string parent_folder_id = parents_to_remove_.back(); 136 parents_to_remove_.pop_back(); 137 138 token->RecordLog(base::StringPrintf( 139 "Detach %s from %s", 140 target_file_id_.c_str(), parent_folder_id.c_str())); 141 142 drive_service()->RemoveResourceFromDirectory( 143 parent_folder_id, target_file_id_, 144 base::Bind(&ConflictResolver::DidDetachFromParent, 145 weak_ptr_factory_.GetWeakPtr(), 146 base::Passed(&token))); 147} 148 149void ConflictResolver::DidDetachFromParent(scoped_ptr<SyncTaskToken> token, 150 google_apis::GDataErrorCode error) { 151 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); 152 if (status != SYNC_STATUS_OK) { 153 SyncTaskManager::NotifyTaskDone(token.Pass(), status); 154 return; 155 } 156 157 if (!parents_to_remove_.empty()) { 158 DetachFromNonPrimaryParents(token.Pass()); 159 return; 160 } 161 162 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_OK); 163} 164 165std::string ConflictResolver::PickPrimaryFile(const TrackerIDSet& trackers) { 166 scoped_ptr<FileMetadata> primary; 167 for (TrackerIDSet::const_iterator itr = trackers.begin(); 168 itr != trackers.end(); ++itr) { 169 FileTracker tracker; 170 if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) { 171 NOTREACHED(); 172 continue; 173 } 174 175 scoped_ptr<FileMetadata> file_metadata(new FileMetadata); 176 if (!metadata_database()->FindFileByFileID( 177 tracker.file_id(), file_metadata.get())) { 178 NOTREACHED(); 179 continue; 180 } 181 182 if (!primary) { 183 primary = file_metadata.Pass(); 184 continue; 185 } 186 187 DCHECK(primary->details().file_kind() == FILE_KIND_FILE || 188 primary->details().file_kind() == FILE_KIND_FOLDER); 189 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE || 190 file_metadata->details().file_kind() == FILE_KIND_FOLDER); 191 192 if (primary->details().file_kind() == FILE_KIND_FILE) { 193 if (file_metadata->details().file_kind() == FILE_KIND_FOLDER) { 194 // Prioritize folders over regular files. 195 primary = file_metadata.Pass(); 196 continue; 197 } 198 199 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE); 200 if (primary->details().modification_time() < 201 file_metadata->details().modification_time()) { 202 // Prioritize last write for regular files. 203 primary = file_metadata.Pass(); 204 continue; 205 } 206 207 continue; 208 } 209 210 DCHECK(primary->details().file_kind() == FILE_KIND_FOLDER); 211 if (file_metadata->details().file_kind() == FILE_KIND_FILE) { 212 // Prioritize folders over regular files. 213 continue; 214 } 215 216 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FOLDER); 217 if (primary->details().creation_time() > 218 file_metadata->details().creation_time()) { 219 // Prioritize first create for folders. 220 primary = file_metadata.Pass(); 221 continue; 222 } 223 } 224 225 if (primary) 226 return primary->file_id(); 227 return std::string(); 228} 229 230void ConflictResolver::RemoveNonPrimaryFiles(scoped_ptr<SyncTaskToken> token) { 231 DCHECK(!non_primary_file_ids_.empty()); 232 233 std::string file_id = non_primary_file_ids_.back().first; 234 std::string etag = non_primary_file_ids_.back().second; 235 non_primary_file_ids_.pop_back(); 236 237 DCHECK_NE(target_file_id_, file_id); 238 239 token->RecordLog(base::StringPrintf( 240 "Remove non-primary file %s", file_id.c_str())); 241 242 // TODO(tzik): Check if the file is a folder, and merge its contents into 243 // the folder identified by |target_file_id_|. 244 drive_service()->DeleteResource( 245 file_id, etag, 246 base::Bind(&ConflictResolver::DidRemoveFile, 247 weak_ptr_factory_.GetWeakPtr(), 248 base::Passed(&token), file_id)); 249} 250 251void ConflictResolver::DidRemoveFile(scoped_ptr<SyncTaskToken> token, 252 const std::string& file_id, 253 google_apis::GDataErrorCode error) { 254 if (error == google_apis::HTTP_PRECONDITION || 255 error == google_apis::HTTP_CONFLICT) { 256 UpdateFileMetadata(file_id, token.Pass()); 257 return; 258 } 259 260 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); 261 if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) { 262 SyncTaskManager::NotifyTaskDone(token.Pass(), status); 263 return; 264 } 265 266 deleted_file_ids_.push_back(file_id); 267 if (!non_primary_file_ids_.empty()) { 268 RemoveNonPrimaryFiles(token.Pass()); 269 return; 270 } 271 272 metadata_database()->UpdateByDeletedRemoteFileList( 273 deleted_file_ids_, SyncTaskToken::WrapToCallback(token.Pass())); 274} 275 276bool ConflictResolver::IsContextReady() { 277 return sync_context_->GetDriveService() && 278 sync_context_->GetMetadataDatabase(); 279} 280 281void ConflictResolver::UpdateFileMetadata( 282 const std::string& file_id, 283 scoped_ptr<SyncTaskToken> token) { 284 drive_service()->GetFileResource( 285 file_id, 286 base::Bind(&ConflictResolver::DidGetRemoteMetadata, 287 weak_ptr_factory_.GetWeakPtr(), file_id, 288 base::Passed(&token))); 289} 290 291void ConflictResolver::DidGetRemoteMetadata( 292 const std::string& file_id, 293 scoped_ptr<SyncTaskToken> token, 294 google_apis::GDataErrorCode error, 295 scoped_ptr<google_apis::FileResource> entry) { 296 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); 297 if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) { 298 SyncTaskManager::NotifyTaskDone(token.Pass(), status); 299 return; 300 } 301 302 if (error != google_apis::HTTP_NOT_FOUND) { 303 metadata_database()->UpdateByDeletedRemoteFile( 304 file_id, SyncTaskToken::WrapToCallback(token.Pass())); 305 return; 306 } 307 308 if (!entry) { 309 NOTREACHED(); 310 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED); 311 return; 312 } 313 314 metadata_database()->UpdateByFileResource( 315 *entry, SyncTaskToken::WrapToCallback(token.Pass())); 316} 317 318drive::DriveServiceInterface* ConflictResolver::drive_service() { 319 set_used_network(true); 320 return sync_context_->GetDriveService(); 321} 322 323MetadataDatabase* ConflictResolver::metadata_database() { 324 return sync_context_->GetMetadataDatabase(); 325} 326 327} // namespace drive_backend 328} // namespace sync_file_system 329