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/drive_backend_util.h"
15 #include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
16 #include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.h"
17 #include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
18 #include "chrome/browser/sync_file_system/logger.h"
19 #include "google_apis/drive/drive_api_parser.h"
21 namespace sync_file_system {
22 namespace drive_backend {
24 ConflictResolver::ConflictResolver(SyncEngineContext* sync_context)
25 : sync_context_(sync_context),
26 weak_ptr_factory_(this) {}
28 ConflictResolver::~ConflictResolver() {}
30 void ConflictResolver::RunExclusive(const SyncStatusCallback& callback) {
31 if (!IsContextReady()) {
33 callback.Run(SYNC_STATUS_FAILED);
37 // Conflict resolution should be invoked on clean tree.
38 if (metadata_database()->HasDirtyTracker()) {
40 callback.Run(SYNC_STATUS_FAILED);
44 TrackerIDSet trackers;
45 if (metadata_database()->GetMultiParentFileTrackers(
46 &target_file_id_, &trackers)) {
47 DCHECK_LT(1u, trackers.size());
48 if (!trackers.has_active()) {
50 callback.Run(SYNC_STATUS_FAILED);
54 util::Log(logging::LOG_VERBOSE, FROM_HERE,
55 "[ConflictResolver] Detected multi-parent trackers "
56 "(active tracker_id=%" PRId64 ")",
57 trackers.active_tracker());
59 DCHECK(trackers.has_active());
60 for (TrackerIDSet::const_iterator itr = trackers.begin();
61 itr != trackers.end(); ++itr) {
63 if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
71 FileTracker parent_tracker;
72 bool should_success = metadata_database()->FindTrackerByTrackerID(
73 tracker.parent_tracker_id(), &parent_tracker);
74 if (!should_success) {
76 callback.Run(SYNC_STATUS_FAILED);
79 parents_to_remove_.push_back(parent_tracker.file_id());
81 DetachFromNonPrimaryParents(callback);
85 if (metadata_database()->GetConflictingTrackers(&trackers)) {
86 target_file_id_ = PickPrimaryFile(trackers);
87 DCHECK(!target_file_id_.empty());
88 int64 primary_tracker_id = -1;
89 for (TrackerIDSet::const_iterator itr = trackers.begin();
90 itr != trackers.end(); ++itr) {
92 if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
96 if (tracker.file_id() != target_file_id_) {
97 non_primary_file_ids_.push_back(
98 std::make_pair(tracker.file_id(), tracker.synced_details().etag()));
100 primary_tracker_id = tracker.tracker_id();
104 util::Log(logging::LOG_VERBOSE, FROM_HERE,
105 "[ConflictResolver] Detected %" PRIuS " conflicting trackers "
106 "(primary tracker_id=%" PRId64 ")",
107 non_primary_file_ids_.size(), primary_tracker_id);
109 RemoveNonPrimaryFiles(callback);
113 callback.Run(SYNC_STATUS_NO_CONFLICT);
116 void ConflictResolver::DetachFromNonPrimaryParents(
117 const SyncStatusCallback& callback) {
118 DCHECK(!parents_to_remove_.empty());
120 // TODO(tzik): Check if ETag match is available for
121 // RemoteResourceFromDirectory.
122 std::string parent_folder_id = parents_to_remove_.back();
123 parents_to_remove_.pop_back();
124 drive_service()->RemoveResourceFromDirectory(
125 parent_folder_id, target_file_id_,
126 base::Bind(&ConflictResolver::DidDetachFromParent,
127 weak_ptr_factory_.GetWeakPtr(),
129 util::Log(logging::LOG_VERBOSE, FROM_HERE,
130 "[ConflictResolver] Detach %s from %s",
131 target_file_id_.c_str(), parent_folder_id.c_str());
134 void ConflictResolver::DidDetachFromParent(const SyncStatusCallback& callback,
135 google_apis::GDataErrorCode error) {
136 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
137 if (status != SYNC_STATUS_OK) {
138 callback.Run(status);
142 if (!parents_to_remove_.empty()) {
143 DetachFromNonPrimaryParents(callback);
147 callback.Run(SYNC_STATUS_OK);
150 std::string ConflictResolver::PickPrimaryFile(const TrackerIDSet& trackers) {
151 scoped_ptr<FileMetadata> primary;
152 for (TrackerIDSet::const_iterator itr = trackers.begin();
153 itr != trackers.end(); ++itr) {
155 if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
160 scoped_ptr<FileMetadata> file_metadata(new FileMetadata);
161 if (!metadata_database()->FindFileByFileID(
162 tracker.file_id(), file_metadata.get())) {
168 primary = file_metadata.Pass();
172 DCHECK(primary->details().file_kind() == FILE_KIND_FILE ||
173 primary->details().file_kind() == FILE_KIND_FOLDER);
174 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE ||
175 file_metadata->details().file_kind() == FILE_KIND_FOLDER);
177 if (primary->details().file_kind() == FILE_KIND_FILE) {
178 if (file_metadata->details().file_kind() == FILE_KIND_FOLDER) {
179 // Prioritize folders over regular files.
180 primary = file_metadata.Pass();
184 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE);
185 if (primary->details().modification_time() <
186 file_metadata->details().modification_time()) {
187 // Prioritize last write for regular files.
188 primary = file_metadata.Pass();
195 DCHECK(primary->details().file_kind() == FILE_KIND_FOLDER);
196 if (file_metadata->details().file_kind() == FILE_KIND_FILE) {
197 // Prioritize folders over regular files.
201 DCHECK(file_metadata->details().file_kind() == FILE_KIND_FOLDER);
202 if (primary->details().creation_time() >
203 file_metadata->details().creation_time()) {
204 // Prioritize first create for folders.
205 primary = file_metadata.Pass();
211 return primary->file_id();
212 return std::string();
215 void ConflictResolver::RemoveNonPrimaryFiles(
216 const SyncStatusCallback& callback) {
217 DCHECK(!non_primary_file_ids_.empty());
219 std::string file_id = non_primary_file_ids_.back().first;
220 std::string etag = non_primary_file_ids_.back().second;
221 non_primary_file_ids_.pop_back();
223 DCHECK_NE(target_file_id_, file_id);
225 util::Log(logging::LOG_VERBOSE, FROM_HERE,
226 "[ConflictResolver] Remove non-primary file %s", file_id.c_str());
228 // TODO(tzik): Check if the file is a folder, and merge its contents into
229 // the folder identified by |target_file_id_|.
230 drive_service()->DeleteResource(
232 base::Bind(&ConflictResolver::DidRemoveFile,
233 weak_ptr_factory_.GetWeakPtr(),
237 void ConflictResolver::DidRemoveFile(const SyncStatusCallback& callback,
238 const std::string& file_id,
239 google_apis::GDataErrorCode error) {
240 if (error == google_apis::HTTP_PRECONDITION ||
241 error == google_apis::HTTP_CONFLICT) {
242 UpdateFileMetadata(file_id, callback);
246 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
247 if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
248 callback.Run(status);
252 deleted_file_ids_.push_back(file_id);
253 if (!non_primary_file_ids_.empty()) {
254 RemoveNonPrimaryFiles(callback);
258 metadata_database()->UpdateByDeletedRemoteFileList(
259 deleted_file_ids_, callback);
262 bool ConflictResolver::IsContextReady() {
263 return sync_context_->GetDriveService() &&
264 sync_context_->GetMetadataDatabase();
267 void ConflictResolver::UpdateFileMetadata(
268 const std::string& file_id,
269 const SyncStatusCallback& callback) {
270 drive_service()->GetResourceEntry(
272 base::Bind(&ConflictResolver::DidGetRemoteMetadata,
273 weak_ptr_factory_.GetWeakPtr(), file_id, callback));
276 void ConflictResolver::DidGetRemoteMetadata(
277 const std::string& file_id,
278 const SyncStatusCallback& callback,
279 google_apis::GDataErrorCode error,
280 scoped_ptr<google_apis::ResourceEntry> entry) {
281 SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
282 if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
283 callback.Run(status);
287 if (error != google_apis::HTTP_NOT_FOUND) {
288 metadata_database()->UpdateByDeletedRemoteFile(file_id, callback);
294 callback.Run(SYNC_STATUS_FAILED);
298 metadata_database()->UpdateByFileResource(
299 *drive::util::ConvertResourceEntryToFileResource(*entry),
303 drive::DriveServiceInterface* ConflictResolver::drive_service() {
304 set_used_network(true);
305 return sync_context_->GetDriveService();
308 MetadataDatabase* ConflictResolver::metadata_database() {
309 return sync_context_->GetMetadataDatabase();
312 } // namespace drive_backend
313 } // namespace sync_file_system