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