Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / sync_file_system / drive_backend / conflict_resolver.cc
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.
4
5 #include "chrome/browser/sync_file_system/drive_backend/conflict_resolver.h"
6
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"
23
24 namespace sync_file_system {
25 namespace drive_backend {
26
27 ConflictResolver::ConflictResolver(SyncEngineContext* sync_context)
28     : sync_context_(sync_context),
29       weak_ptr_factory_(this) {}
30
31 ConflictResolver::~ConflictResolver() {}
32
33 void ConflictResolver::RunPreflight(scoped_ptr<SyncTaskToken> token) {
34   token->InitializeTaskLog("Conflict Resolution");
35
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()));
42 }
43
44 void ConflictResolver::RunExclusive(scoped_ptr<SyncTaskToken> token) {
45   if (!IsContextReady()) {
46     SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
47     return;
48   }
49
50   // Conflict resolution should be invoked on clean tree.
51   if (metadata_database()->HasDirtyTracker()) {
52     NOTREACHED();
53     SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
54     return;
55   }
56
57   TrackerIDSet trackers;
58   if (metadata_database()->GetMultiParentFileTrackers(
59           &target_file_id_, &trackers)) {
60     DCHECK_LT(1u, trackers.size());
61     if (!trackers.has_active()) {
62       NOTREACHED();
63       SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
64       return;
65     }
66
67     token->RecordLog(base::StringPrintf(
68         "Detected multi-parent trackers (active tracker_id=%" PRId64 ")",
69         trackers.active_tracker()));
70
71     DCHECK(trackers.has_active());
72     for (TrackerIDSet::const_iterator itr = trackers.begin();
73          itr != trackers.end(); ++itr) {
74       FileTracker tracker;
75       if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
76         NOTREACHED();
77         continue;
78       }
79
80       if (tracker.active())
81         continue;
82
83       FileTracker parent_tracker;
84       bool should_success = metadata_database()->FindTrackerByTrackerID(
85           tracker.parent_tracker_id(), &parent_tracker);
86       if (!should_success) {
87         NOTREACHED();
88         SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
89         return;
90       }
91       parents_to_remove_.push_back(parent_tracker.file_id());
92     }
93     DetachFromNonPrimaryParents(token.Pass());
94     return;
95   }
96
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) {
103       FileTracker tracker;
104       if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
105         NOTREACHED();
106         continue;
107       }
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()));
111       } else {
112         primary_tracker_id = tracker.tracker_id();
113       }
114     }
115
116     token->RecordLog(base::StringPrintf(
117         "Detected %" PRIuS " conflicting trackers "
118         "(primary tracker_id=%" PRId64 ")",
119         non_primary_file_ids_.size(), primary_tracker_id));
120
121     RemoveNonPrimaryFiles(token.Pass());
122     return;
123   }
124
125   SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_NO_CONFLICT);
126 }
127
128 void ConflictResolver::DetachFromNonPrimaryParents(
129     scoped_ptr<SyncTaskToken> token) {
130   DCHECK(!parents_to_remove_.empty());
131
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();
136
137   token->RecordLog(base::StringPrintf(
138       "Detach %s from %s",
139       target_file_id_.c_str(), parent_folder_id.c_str()));
140
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)));
146 }
147
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);
153     return;
154   }
155
156   if (!parents_to_remove_.empty()) {
157     DetachFromNonPrimaryParents(token.Pass());
158     return;
159   }
160
161   SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_OK);
162 }
163
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) {
168     FileTracker tracker;
169     if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
170       NOTREACHED();
171       continue;
172     }
173
174     scoped_ptr<FileMetadata> file_metadata(new FileMetadata);
175     if (!metadata_database()->FindFileByFileID(
176             tracker.file_id(), file_metadata.get())) {
177       NOTREACHED();
178       continue;
179     }
180
181     if (!primary) {
182       primary = file_metadata.Pass();
183       continue;
184     }
185
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);
190
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();
195         continue;
196       }
197
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();
203         continue;
204       }
205
206       continue;
207     }
208
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.
212       continue;
213     }
214
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();
220       continue;
221     }
222   }
223
224   if (primary)
225     return primary->file_id();
226   return std::string();
227 }
228
229 void ConflictResolver::RemoveNonPrimaryFiles(scoped_ptr<SyncTaskToken> token) {
230   DCHECK(!non_primary_file_ids_.empty());
231
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();
235
236   DCHECK_NE(target_file_id_, file_id);
237
238   token->RecordLog(base::StringPrintf(
239       "Remove non-primary file %s", file_id.c_str()));
240
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(
244       file_id, etag,
245       base::Bind(&ConflictResolver::DidRemoveFile,
246                  weak_ptr_factory_.GetWeakPtr(),
247                  base::Passed(&token), file_id));
248 }
249
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());
256     return;
257   }
258
259   SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
260   if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
261     SyncTaskManager::NotifyTaskDone(token.Pass(), status);
262     return;
263   }
264
265   deleted_file_ids_.push_back(file_id);
266   if (!non_primary_file_ids_.empty()) {
267     RemoveNonPrimaryFiles(token.Pass());
268     return;
269   }
270
271   status = metadata_database()->UpdateByDeletedRemoteFileList(
272       deleted_file_ids_);
273   SyncTaskManager::NotifyTaskDone(token.Pass(), status);
274 }
275
276 bool ConflictResolver::IsContextReady() {
277   return sync_context_->GetDriveService() &&
278       sync_context_->GetMetadataDatabase();
279 }
280
281 void ConflictResolver::UpdateFileMetadata(
282     const std::string& file_id,
283     scoped_ptr<SyncTaskToken> token) {
284   drive_service()->GetFileResource(
285       file_id,
286       base::Bind(&ConflictResolver::DidGetRemoteMetadata,
287                  weak_ptr_factory_.GetWeakPtr(), file_id,
288                  base::Passed(&token)));
289 }
290
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);
299     return;
300   }
301
302   if (error != google_apis::HTTP_NOT_FOUND) {
303     status = metadata_database()->UpdateByDeletedRemoteFile(file_id);
304     SyncTaskManager::NotifyTaskDone(token.Pass(), status);
305     return;
306   }
307
308   if (!entry) {
309     NOTREACHED();
310     SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
311     return;
312   }
313
314   status = metadata_database()->UpdateByFileResource(*entry);
315   SyncTaskManager::NotifyTaskDone(token.Pass(), status);
316 }
317
318 drive::DriveServiceInterface* ConflictResolver::drive_service() {
319   set_used_network(true);
320   return sync_context_->GetDriveService();
321 }
322
323 MetadataDatabase* ConflictResolver::metadata_database() {
324   return sync_context_->GetMetadataDatabase();
325 }
326
327 }  // namespace drive_backend
328 }  // namespace sync_file_system