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.
5 #include "chrome/browser/sync_file_system/drive_backend/conflict_resolver.h"
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"
24 namespace sync_file_system {
25 namespace drive_backend {
27 ConflictResolver::ConflictResolver(SyncEngineContext* sync_context)
28 : sync_context_(sync_context),
29 weak_ptr_factory_(this) {}
31 ConflictResolver::~ConflictResolver() {}
33 void ConflictResolver::RunPreflight(scoped_ptr<SyncTaskToken> token) {
34 token->InitializeTaskLog("Conflict Resolution");
36 scoped_ptr<TaskBlocker> task_blocker(new TaskBlocker);
37 task_blocker->exclusive = true;
38 SyncTaskManager::UpdateTaskBlocker(
39 token.Pass(), task_blocker.Pass(),
40 base::Bind(&ConflictResolver::RunExclusive,
41 weak_ptr_factory_.GetWeakPtr()));
44 void ConflictResolver::RunExclusive(scoped_ptr<SyncTaskToken> token) {
45 if (!IsContextReady()) {
46 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
50 // Conflict resolution should be invoked on clean tree.
51 if (metadata_database()->HasDirtyTracker()) {
53 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
57 TrackerIDSet trackers;
58 if (metadata_database()->GetMultiParentFileTrackers(
59 &target_file_id_, &trackers)) {
60 DCHECK_LT(1u, trackers.size());
61 if (!trackers.has_active()) {
63 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
67 token->RecordLog(base::StringPrintf(
68 "Detected multi-parent trackers (active tracker_id=%" PRId64 ")",
69 trackers.active_tracker()));
71 DCHECK(trackers.has_active());
72 for (TrackerIDSet::const_iterator itr = trackers.begin();
73 itr != trackers.end(); ++itr) {
75 if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
83 FileTracker parent_tracker;
84 bool should_success = metadata_database()->FindTrackerByTrackerID(
85 tracker.parent_tracker_id(), &parent_tracker);
86 if (!should_success) {
88 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
91 parents_to_remove_.push_back(parent_tracker.file_id());
93 DetachFromNonPrimaryParents(token.Pass());
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) {
104 if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
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()));
112 primary_tracker_id = tracker.tracker_id();
116 token->RecordLog(base::StringPrintf(
117 "Detected %" PRIuS " conflicting trackers "
118 "(primary tracker_id=%" PRId64 ")",
119 non_primary_file_ids_.size(), primary_tracker_id));
121 RemoveNonPrimaryFiles(token.Pass());
125 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_NO_CONFLICT);
128 void ConflictResolver::DetachFromNonPrimaryParents(
129 scoped_ptr<SyncTaskToken> token) {
130 DCHECK(!parents_to_remove_.empty());
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();
137 token->RecordLog(base::StringPrintf(
139 target_file_id_.c_str(), parent_folder_id.c_str()));
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)));
148 void 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);
156 if (!parents_to_remove_.empty()) {
157 DetachFromNonPrimaryParents(token.Pass());
161 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_OK);
164 std::string ConflictResolver::PickPrimaryFile(const TrackerIDSet& trackers) {
165 scoped_ptr<FileMetadata> primary;
166 for (TrackerIDSet::const_iterator itr = trackers.begin();
167 itr != trackers.end(); ++itr) {
169 if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
174 scoped_ptr<FileMetadata> file_metadata(new FileMetadata);
175 if (!metadata_database()->FindFileByFileID(
176 tracker.file_id(), file_metadata.get())) {
182 primary = file_metadata.Pass();
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);
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();
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();
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.
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();
225 return primary->file_id();
226 return std::string();
229 void ConflictResolver::RemoveNonPrimaryFiles(scoped_ptr<SyncTaskToken> token) {
230 DCHECK(!non_primary_file_ids_.empty());
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();
236 DCHECK_NE(target_file_id_, file_id);
238 token->RecordLog(base::StringPrintf(
239 "Remove non-primary file %s", file_id.c_str()));
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(
245 base::Bind(&ConflictResolver::DidRemoveFile,
246 weak_ptr_factory_.GetWeakPtr(),
247 base::Passed(&token), file_id));
250 void 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());
259 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
260 if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
261 SyncTaskManager::NotifyTaskDone(token.Pass(), status);
265 deleted_file_ids_.push_back(file_id);
266 if (!non_primary_file_ids_.empty()) {
267 RemoveNonPrimaryFiles(token.Pass());
271 status = metadata_database()->UpdateByDeletedRemoteFileList(
273 SyncTaskManager::NotifyTaskDone(token.Pass(), status);
276 bool ConflictResolver::IsContextReady() {
277 return sync_context_->GetDriveService() &&
278 sync_context_->GetMetadataDatabase();
281 void ConflictResolver::UpdateFileMetadata(
282 const std::string& file_id,
283 scoped_ptr<SyncTaskToken> token) {
284 drive_service()->GetFileResource(
286 base::Bind(&ConflictResolver::DidGetRemoteMetadata,
287 weak_ptr_factory_.GetWeakPtr(), file_id,
288 base::Passed(&token)));
291 void 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);
302 if (error != google_apis::HTTP_NOT_FOUND) {
303 status = metadata_database()->UpdateByDeletedRemoteFile(file_id);
304 SyncTaskManager::NotifyTaskDone(token.Pass(), status);
310 SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
314 status = metadata_database()->UpdateByFileResource(*entry);
315 SyncTaskManager::NotifyTaskDone(token.Pass(), status);
318 drive::DriveServiceInterface* ConflictResolver::drive_service() {
319 set_used_network(true);
320 return sync_context_->GetDriveService();
323 MetadataDatabase* ConflictResolver::metadata_database() {
324 return sync_context_->GetMetadataDatabase();
327 } // namespace drive_backend
328 } // namespace sync_file_system