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 deleted_file_ids_
.push_back(file_id
);
215 DCHECK_NE(target_file_id_
, file_id
);
217 util::Log(logging::LOG_VERBOSE
, FROM_HERE
,
218 "[ConflictResolver] Remove non-primary file %s", file_id
.c_str());
220 // TODO(tzik): Check if the file is a folder, and merge its contents into
221 // the folder identified by |target_file_id_|.
222 drive_service()->DeleteResource(
224 base::Bind(&ConflictResolver::DidRemoveFile
,
225 weak_ptr_factory_
.GetWeakPtr(),
229 void ConflictResolver::DidRemoveFile(const SyncStatusCallback
& callback
,
230 const std::string
& file_id
,
231 google_apis::GDataErrorCode error
) {
232 if (error
== google_apis::HTTP_PRECONDITION
||
233 error
== google_apis::HTTP_CONFLICT
) {
234 UpdateFileMetadata(file_id
, callback
);
238 SyncStatusCode status
= GDataErrorCodeToSyncStatusCode(error
);
239 if (status
!= SYNC_STATUS_OK
&& error
!= google_apis::HTTP_NOT_FOUND
) {
240 callback
.Run(status
);
244 deleted_file_ids_
.push_back(file_id
);
245 if (!non_primary_file_ids_
.empty()) {
246 RemoveNonPrimaryFiles(callback
);
250 metadata_database()->UpdateByDeletedRemoteFileList(
251 deleted_file_ids_
, callback
);
254 bool ConflictResolver::IsContextReady() {
255 return sync_context_
->GetDriveService() &&
256 sync_context_
->GetMetadataDatabase();
259 void ConflictResolver::UpdateFileMetadata(
260 const std::string
& file_id
,
261 const SyncStatusCallback
& callback
) {
262 drive_service()->GetResourceEntry(
264 base::Bind(&ConflictResolver::DidGetRemoteMetadata
,
265 weak_ptr_factory_
.GetWeakPtr(), file_id
, callback
));
268 void ConflictResolver::DidGetRemoteMetadata(
269 const std::string
& file_id
,
270 const SyncStatusCallback
& callback
,
271 google_apis::GDataErrorCode error
,
272 scoped_ptr
<google_apis::ResourceEntry
> entry
) {
273 SyncStatusCode status
= GDataErrorCodeToSyncStatusCode(error
);
274 if (status
!= SYNC_STATUS_OK
&& error
!= google_apis::HTTP_NOT_FOUND
) {
275 callback
.Run(status
);
279 if (error
!= google_apis::HTTP_NOT_FOUND
) {
280 metadata_database()->UpdateByDeletedRemoteFile(file_id
, callback
);
286 callback
.Run(SYNC_STATUS_FAILED
);
290 metadata_database()->UpdateByFileResource(
291 *drive::util::ConvertResourceEntryToFileResource(*entry
),
295 drive::DriveServiceInterface
* ConflictResolver::drive_service() {
296 set_used_network(true);
297 return sync_context_
->GetDriveService();
300 MetadataDatabase
* ConflictResolver::metadata_database() {
301 return sync_context_
->GetMetadataDatabase();
304 } // namespace drive_backend
305 } // namespace sync_file_system