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 "chrome/browser/drive/drive_api_util.h"
12 #include "chrome/browser/drive/drive_service_interface.h"
13 #include "chrome/browser/drive/drive_uploader.h"
14 #include "chrome/browser/sync_file_system/drive_backend/conflict_resolver.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/logger.h"
20 #include "google_apis/drive/drive_api_parser.h"
22 namespace sync_file_system {
23 namespace drive_backend {
25 ConflictResolver::ConflictResolver(SyncEngineContext* sync_context)
26 : sync_context_(sync_context),
27 weak_ptr_factory_(this) {
30 ConflictResolver::~ConflictResolver() {
33 void ConflictResolver::Run(const SyncStatusCallback& callback) {
34 if (!IsContextReady()) {
36 callback.Run(SYNC_STATUS_FAILED);
40 // Conflict resolution should be invoked on clean tree.
41 if (metadata_database()->GetNormalPriorityDirtyTracker(NULL) ||
42 metadata_database()->GetLowPriorityDirtyTracker(NULL)) {
44 callback.Run(SYNC_STATUS_FAILED);
49 if (metadata_database()->GetMultiParentFileTrackers(
50 &target_file_id_, &trackers)) {
51 DCHECK_LT(1u, trackers.size());
52 if (!trackers.has_active()) {
54 callback.Run(SYNC_STATUS_FAILED);
58 util::Log(logging::LOG_VERBOSE, FROM_HERE,
59 "[ConflictResolver] Detected multi-parent trackers "
60 "(active tracker_id=%" PRId64 ")",
61 trackers.active_tracker()->tracker_id());
63 DCHECK(trackers.has_active());
64 for (TrackerSet::const_iterator itr = trackers.begin();
65 itr != trackers.end(); ++itr) {
66 const FileTracker& tracker = **itr;
70 FileTracker parent_tracker;
71 bool should_success = metadata_database()->FindTrackerByTrackerID(
72 tracker.parent_tracker_id(), &parent_tracker);
73 if (!should_success) {
75 callback.Run(SYNC_STATUS_FAILED);
78 parents_to_remove_.push_back(parent_tracker.file_id());
80 DetachFromNonPrimaryParents(callback);
84 if (metadata_database()->GetConflictingTrackers(&trackers)) {
85 target_file_id_ = PickPrimaryFile(trackers);
86 DCHECK(!target_file_id_.empty());
87 int64 primary_tracker_id = -1;
88 for (TrackerSet::const_iterator itr = trackers.begin();
89 itr != trackers.end(); ++itr) {
90 const FileTracker& tracker = **itr;
91 if (tracker.file_id() != target_file_id_) {
92 non_primary_file_ids_.push_back(
93 std::make_pair(tracker.file_id(), tracker.synced_details().etag()));
95 primary_tracker_id = tracker.tracker_id();
99 util::Log(logging::LOG_VERBOSE, FROM_HERE,
100 "[ConflictResolver] Detected %" PRIuS " conflicting trackers "
101 "(primary tracker_id=%" PRId64 ")",
102 non_primary_file_ids_.size(), primary_tracker_id);
104 RemoveNonPrimaryFiles(callback);
108 callback.Run(SYNC_STATUS_NO_CONFLICT);
111 void ConflictResolver::DetachFromNonPrimaryParents(
112 const SyncStatusCallback& callback) {
113 DCHECK(!parents_to_remove_.empty());
115 // TODO(tzik): Check if ETag match is available for
116 // RemoteResourceFromDirectory.
117 std::string parent_folder_id = parents_to_remove_.back();
118 parents_to_remove_.pop_back();
119 drive_service()->RemoveResourceFromDirectory(
120 parent_folder_id, target_file_id_,
121 base::Bind(&ConflictResolver::DidDetachFromParent,
122 weak_ptr_factory_.GetWeakPtr(),
124 util::Log(logging::LOG_VERBOSE, FROM_HERE,
125 "[ConflictResolver] Detach %s from %s",
126 target_file_id_.c_str(), parent_folder_id.c_str());
129 void ConflictResolver::DidDetachFromParent(const SyncStatusCallback& callback,
130 google_apis::GDataErrorCode error) {
131 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
132 if (status != SYNC_STATUS_OK) {
133 callback.Run(status);
137 if (!parents_to_remove_.empty()) {
138 DetachFromNonPrimaryParents(callback);
142 callback.Run(SYNC_STATUS_OK);
145 std::string ConflictResolver::PickPrimaryFile(const TrackerSet& trackers) {
146 scoped_ptr<FileMetadata> primary;
147 for (TrackerSet::const_iterator itr = trackers.begin();
148 itr != trackers.end(); ++itr) {
149 const FileTracker& tracker = **itr;
150 scoped_ptr<FileMetadata> file_metadata(new FileMetadata);
151 bool should_success = metadata_database()->FindFileByFileID(
152 tracker.file_id(), file_metadata.get());
153 if (!should_success) {
159 primary = file_metadata.Pass();
163 DCHECK(primary->details().file_kind() == FILE_KIND_FILE ||
164 primary->details().file_kind() == FILE_KIND_FOLDER);
165 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE ||
166 file_metadata->details().file_kind() == FILE_KIND_FOLDER);
168 if (primary->details().file_kind() == FILE_KIND_FILE) {
169 if (file_metadata->details().file_kind() == FILE_KIND_FOLDER) {
170 // Prioritize folders over regular files.
171 primary = file_metadata.Pass();
175 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE);
176 if (primary->details().modification_time() <
177 file_metadata->details().modification_time()) {
178 // Prioritize last write for regular files.
179 primary = file_metadata.Pass();
186 DCHECK(primary->details().file_kind() == FILE_KIND_FOLDER);
187 if (file_metadata->details().file_kind() == FILE_KIND_FILE) {
188 // Prioritize folders over regular files.
192 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FOLDER);
193 if (primary->details().creation_time() >
194 file_metadata->details().creation_time()) {
195 // Prioritize first create for folders.
196 primary = file_metadata.Pass();
202 return primary->file_id();
203 return std::string();
206 void ConflictResolver::RemoveNonPrimaryFiles(
207 const SyncStatusCallback& callback) {
208 DCHECK(!non_primary_file_ids_.empty());
210 std::string file_id = non_primary_file_ids_.back().first;
211 std::string etag = non_primary_file_ids_.back().second;
212 non_primary_file_ids_.pop_back();
214 DCHECK_NE(target_file_id_, file_id);
216 util::Log(logging::LOG_VERBOSE, FROM_HERE,
217 "[ConflictResolver] Remove non-primary file %s", file_id.c_str());
219 // TODO(tzik): Check if the file is a folder, and merge its contents into
220 // the folder identified by |target_file_id_|.
221 drive_service()->DeleteResource(
223 base::Bind(&ConflictResolver::DidRemoveFile,
224 weak_ptr_factory_.GetWeakPtr(),
228 void ConflictResolver::DidRemoveFile(const SyncStatusCallback& callback,
229 const std::string& file_id,
230 google_apis::GDataErrorCode error) {
231 if (error == google_apis::HTTP_PRECONDITION ||
232 error == google_apis::HTTP_CONFLICT) {
233 UpdateFileMetadata(file_id, callback);
237 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
238 if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
239 callback.Run(status);
243 deleted_file_ids_.push_back(file_id);
244 if (!non_primary_file_ids_.empty()) {
245 RemoveNonPrimaryFiles(callback);
249 metadata_database()->UpdateByDeletedRemoteFileList(
250 deleted_file_ids_, callback);
253 bool ConflictResolver::IsContextReady() {
254 return sync_context_->GetDriveService() &&
255 sync_context_->GetMetadataDatabase();
258 void ConflictResolver::UpdateFileMetadata(
259 const std::string& file_id,
260 const SyncStatusCallback& callback) {
261 drive_service()->GetResourceEntry(
263 base::Bind(&ConflictResolver::DidGetRemoteMetadata,
264 weak_ptr_factory_.GetWeakPtr(), file_id, callback));
267 void ConflictResolver::DidGetRemoteMetadata(
268 const std::string& file_id,
269 const SyncStatusCallback& callback,
270 google_apis::GDataErrorCode error,
271 scoped_ptr<google_apis::ResourceEntry> entry) {
272 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
273 if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
274 callback.Run(status);
278 if (error != google_apis::HTTP_NOT_FOUND) {
279 metadata_database()->UpdateByDeletedRemoteFile(file_id, callback);
285 callback.Run(SYNC_STATUS_FAILED);
289 metadata_database()->UpdateByFileResource(
290 *drive::util::ConvertResourceEntryToFileResource(*entry),
294 drive::DriveServiceInterface* ConflictResolver::drive_service() {
295 set_used_network(true);
296 return sync_context_->GetDriveService();
299 MetadataDatabase* ConflictResolver::metadata_database() {
300 return sync_context_->GetMetadataDatabase();
303 } // namespace drive_backend
304 } // namespace sync_file_system