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()) {
52 SyncTaskManager::NotifyTaskDone(token
.Pass(), SYNC_STATUS_RETRY
);
56 TrackerIDSet trackers
;
57 if (metadata_database()->GetMultiParentFileTrackers(
58 &target_file_id_
, &trackers
)) {
59 DCHECK_LT(1u, trackers
.size());
60 if (!trackers
.has_active()) {
62 SyncTaskManager::NotifyTaskDone(token
.Pass(), SYNC_STATUS_FAILED
);
66 token
->RecordLog(base::StringPrintf(
67 "Detected multi-parent trackers (active tracker_id=%" PRId64
")",
68 trackers
.active_tracker()));
70 DCHECK(trackers
.has_active());
71 for (TrackerIDSet::const_iterator itr
= trackers
.begin();
72 itr
!= trackers
.end(); ++itr
) {
74 if (!metadata_database()->FindTrackerByTrackerID(*itr
, &tracker
)) {
82 FileTracker parent_tracker
;
83 bool should_success
= metadata_database()->FindTrackerByTrackerID(
84 tracker
.parent_tracker_id(), &parent_tracker
);
85 if (!should_success
) {
87 SyncTaskManager::NotifyTaskDone(token
.Pass(), SYNC_STATUS_FAILED
);
90 parents_to_remove_
.push_back(parent_tracker
.file_id());
92 DetachFromNonPrimaryParents(token
.Pass());
96 if (metadata_database()->GetConflictingTrackers(&trackers
)) {
97 target_file_id_
= PickPrimaryFile(trackers
);
98 DCHECK(!target_file_id_
.empty());
99 int64 primary_tracker_id
= -1;
100 for (TrackerIDSet::const_iterator itr
= trackers
.begin();
101 itr
!= trackers
.end(); ++itr
) {
103 if (!metadata_database()->FindTrackerByTrackerID(*itr
, &tracker
)) {
107 if (tracker
.file_id() != target_file_id_
) {
108 non_primary_file_ids_
.push_back(
109 std::make_pair(tracker
.file_id(), tracker
.synced_details().etag()));
111 primary_tracker_id
= tracker
.tracker_id();
115 token
->RecordLog(base::StringPrintf(
116 "Detected %" PRIuS
" conflicting trackers "
117 "(primary tracker_id=%" PRId64
")",
118 non_primary_file_ids_
.size(), primary_tracker_id
));
120 RemoveNonPrimaryFiles(token
.Pass());
124 SyncTaskManager::NotifyTaskDone(token
.Pass(), SYNC_STATUS_NO_CONFLICT
);
127 void ConflictResolver::DetachFromNonPrimaryParents(
128 scoped_ptr
<SyncTaskToken
> token
) {
129 DCHECK(!parents_to_remove_
.empty());
131 // TODO(tzik): Check if ETag match is available for
132 // RemoteResourceFromDirectory.
133 std::string parent_folder_id
= parents_to_remove_
.back();
134 parents_to_remove_
.pop_back();
136 token
->RecordLog(base::StringPrintf(
138 target_file_id_
.c_str(), parent_folder_id
.c_str()));
140 drive_service()->RemoveResourceFromDirectory(
141 parent_folder_id
, target_file_id_
,
142 base::Bind(&ConflictResolver::DidDetachFromParent
,
143 weak_ptr_factory_
.GetWeakPtr(),
144 base::Passed(&token
)));
147 void ConflictResolver::DidDetachFromParent(
148 scoped_ptr
<SyncTaskToken
> token
,
149 google_apis::DriveApiErrorCode error
) {
150 SyncStatusCode status
= DriveApiErrorCodeToSyncStatusCode(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::DriveApiErrorCode error
) {
253 if (error
== google_apis::HTTP_PRECONDITION
||
254 error
== google_apis::HTTP_CONFLICT
) {
255 UpdateFileMetadata(file_id
, token
.Pass());
259 SyncStatusCode status
= DriveApiErrorCodeToSyncStatusCode(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::DriveApiErrorCode error
,
295 scoped_ptr
<google_apis::FileResource
> entry
) {
296 SyncStatusCode status
= DriveApiErrorCodeToSyncStatusCode(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