conflict_resolver.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/conflict_resolver.h" 6 7#include "base/callback.h" 8#include "base/location.h" 9#include "base/logging.h" 10#include "chrome/browser/drive/drive_api_util.h" 11#include "chrome/browser/drive/drive_service_interface.h" 12#include "chrome/browser/drive/drive_uploader.h" 13#include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h" 14#include "chrome/browser/sync_file_system/drive_backend/metadata_database.h" 15#include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.h" 16#include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h" 17#include "chrome/browser/sync_file_system/drive_backend_v1/drive_file_sync_util.h" 18#include "google_apis/drive/drive_api_parser.h" 19 20namespace sync_file_system { 21namespace drive_backend { 22 23ConflictResolver::ConflictResolver(SyncEngineContext* sync_context) 24 : sync_context_(sync_context), 25 weak_ptr_factory_(this) { 26} 27 28ConflictResolver::~ConflictResolver() { 29} 30 31void ConflictResolver::Run(const SyncStatusCallback& callback) { 32 if (!IsContextReady()) { 33 NOTREACHED(); 34 callback.Run(SYNC_STATUS_FAILED); 35 return; 36 } 37 38 // Conflict resolution should be invoked on clean tree. 39 if (metadata_database()->GetNormalPriorityDirtyTracker(NULL) || 40 metadata_database()->GetLowPriorityDirtyTracker(NULL)) { 41 NOTREACHED(); 42 callback.Run(SYNC_STATUS_FAILED); 43 return; 44 } 45 46 TrackerSet trackers; 47 if (metadata_database()->GetMultiParentFileTrackers( 48 &target_file_id_, &trackers)) { 49 DCHECK_LT(1u, trackers.size()); 50 if (!trackers.has_active()) { 51 NOTREACHED(); 52 callback.Run(SYNC_STATUS_FAILED); 53 return; 54 } 55 56 DCHECK(trackers.has_active()); 57 for (TrackerSet::const_iterator itr = trackers.begin(); 58 itr != trackers.end(); ++itr) { 59 const FileTracker& tracker = **itr; 60 if (tracker.active()) 61 continue; 62 63 FileTracker parent_tracker; 64 bool should_success = metadata_database()->FindTrackerByTrackerID( 65 tracker.parent_tracker_id(), &parent_tracker); 66 if (!should_success) { 67 NOTREACHED(); 68 callback.Run(SYNC_STATUS_FAILED); 69 return; 70 } 71 parents_to_remove_.push_back(parent_tracker.file_id()); 72 } 73 DetachFromNonPrimaryParents(callback); 74 return; 75 } 76 77 if (metadata_database()->GetConflictingTrackers(&trackers)) { 78 target_file_id_ = PickPrimaryFile(trackers); 79 DCHECK(!target_file_id_.empty()); 80 for (TrackerSet::const_iterator itr = trackers.begin(); 81 itr != trackers.end(); ++itr) { 82 const FileTracker& tracker = **itr; 83 if (tracker.file_id() != target_file_id_) { 84 non_primary_file_ids_.push_back( 85 std::make_pair(tracker.file_id(), tracker.synced_details().etag())); 86 } 87 } 88 RemoveNonPrimaryFiles(callback); 89 return; 90 } 91 92 callback.Run(SYNC_STATUS_NO_CONFLICT); 93} 94 95void ConflictResolver::DetachFromNonPrimaryParents( 96 const SyncStatusCallback& callback) { 97 DCHECK(!parents_to_remove_.empty()); 98 99 // TODO(tzik): Check if ETag match is available for 100 // RemoteResourceFromDirectory. 101 std::string parent_folder_id = parents_to_remove_.back(); 102 parents_to_remove_.pop_back(); 103 drive_service()->RemoveResourceFromDirectory( 104 parent_folder_id, target_file_id_, 105 base::Bind(&ConflictResolver::DidDetachFromParent, 106 weak_ptr_factory_.GetWeakPtr(), 107 callback)); 108} 109 110void ConflictResolver::DidDetachFromParent(const SyncStatusCallback& callback, 111 google_apis::GDataErrorCode error) { 112 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); 113 if (status != SYNC_STATUS_OK) { 114 callback.Run(status); 115 return; 116 } 117 118 if (!parents_to_remove_.empty()) { 119 DetachFromNonPrimaryParents(callback); 120 return; 121 } 122 123 callback.Run(SYNC_STATUS_OK); 124} 125 126std::string ConflictResolver::PickPrimaryFile(const TrackerSet& trackers) { 127 scoped_ptr<FileMetadata> primary; 128 for (TrackerSet::const_iterator itr = trackers.begin(); 129 itr != trackers.end(); ++itr) { 130 const FileTracker& tracker = **itr; 131 scoped_ptr<FileMetadata> file_metadata(new FileMetadata); 132 bool should_success = metadata_database()->FindFileByFileID( 133 tracker.file_id(), file_metadata.get()); 134 if (!should_success) { 135 NOTREACHED(); 136 continue; 137 } 138 139 if (!primary) { 140 primary = file_metadata.Pass(); 141 continue; 142 } 143 144 DCHECK(primary->details().file_kind() == FILE_KIND_FILE || 145 primary->details().file_kind() == FILE_KIND_FOLDER); 146 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE || 147 file_metadata->details().file_kind() == FILE_KIND_FOLDER); 148 149 if (primary->details().file_kind() == FILE_KIND_FILE) { 150 if (file_metadata->details().file_kind() == FILE_KIND_FOLDER) { 151 // Prioritize folders over regular files. 152 primary = file_metadata.Pass(); 153 continue; 154 } 155 156 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE); 157 if (primary->details().modification_time() < 158 file_metadata->details().modification_time()) { 159 // Prioritize last write for regular files. 160 primary = file_metadata.Pass(); 161 continue; 162 } 163 164 continue; 165 } 166 167 DCHECK(primary->details().file_kind() == FILE_KIND_FOLDER); 168 if (file_metadata->details().file_kind() == FILE_KIND_FILE) { 169 // Prioritize folders over regular files. 170 continue; 171 } 172 173 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FOLDER); 174 if (primary->details().creation_time() > 175 file_metadata->details().creation_time()) { 176 // Prioritize first create for folders. 177 primary = file_metadata.Pass(); 178 continue; 179 } 180 } 181 182 if (primary) 183 return primary->file_id(); 184 return std::string(); 185} 186 187void ConflictResolver::RemoveNonPrimaryFiles( 188 const SyncStatusCallback& callback) { 189 DCHECK(!non_primary_file_ids_.empty()); 190 191 std::string file_id = non_primary_file_ids_.back().first; 192 std::string etag = non_primary_file_ids_.back().second; 193 non_primary_file_ids_.pop_back(); 194 DCHECK_NE(target_file_id_, file_id); 195 196 // TODO(tzik): Check if the file is a folder, and merge its contents into 197 // the folder identified by |target_file_id_|. 198 drive_service()->DeleteResource( 199 file_id, etag, 200 base::Bind(&ConflictResolver::DidRemoveFile, 201 weak_ptr_factory_.GetWeakPtr(), 202 callback, file_id)); 203} 204 205void ConflictResolver::DidRemoveFile(const SyncStatusCallback& callback, 206 const std::string& file_id, 207 google_apis::GDataErrorCode error) { 208 if (error == google_apis::HTTP_PRECONDITION || 209 error == google_apis::HTTP_CONFLICT) { 210 callback.Run(SYNC_STATUS_RETRY); 211 return; 212 } 213 214 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error); 215 if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) { 216 callback.Run(status); 217 return; 218 } 219 220 if (!non_primary_file_ids_.empty()) { 221 RemoveNonPrimaryFiles(callback); 222 return; 223 } 224 225 callback.Run(SYNC_STATUS_OK); 226} 227 228bool ConflictResolver::IsContextReady() { 229 return sync_context_->GetDriveService() && 230 sync_context_->GetMetadataDatabase(); 231} 232 233drive::DriveServiceInterface* ConflictResolver::drive_service() { 234 set_used_network(true); 235 return sync_context_->GetDriveService(); 236} 237 238MetadataDatabase* ConflictResolver::metadata_database() { 239 return sync_context_->GetMetadataDatabase(); 240} 241 242} // namespace drive_backend 243} // namespace sync_file_system 244