Upstream version 7.36.149.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 "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"
20
21 namespace sync_file_system {
22 namespace drive_backend {
23
24 ConflictResolver::ConflictResolver(SyncEngineContext* sync_context)
25     : sync_context_(sync_context),
26       weak_ptr_factory_(this) {}
27
28 ConflictResolver::~ConflictResolver() {}
29
30 void ConflictResolver::RunExclusive(const SyncStatusCallback& callback) {
31   if (!IsContextReady()) {
32     NOTREACHED();
33     callback.Run(SYNC_STATUS_FAILED);
34     return;
35   }
36
37   // Conflict resolution should be invoked on clean tree.
38   if (metadata_database()->HasDirtyTracker()) {
39     NOTREACHED();
40     callback.Run(SYNC_STATUS_FAILED);
41     return;
42   }
43
44   TrackerIDSet trackers;
45   if (metadata_database()->GetMultiParentFileTrackers(
46           &target_file_id_, &trackers)) {
47     DCHECK_LT(1u, trackers.size());
48     if (!trackers.has_active()) {
49       NOTREACHED();
50       callback.Run(SYNC_STATUS_FAILED);
51       return;
52     }
53
54     util::Log(logging::LOG_VERBOSE, FROM_HERE,
55               "[ConflictResolver] Detected multi-parent trackers "
56               "(active tracker_id=%" PRId64 ")",
57               trackers.active_tracker());
58
59     DCHECK(trackers.has_active());
60     for (TrackerIDSet::const_iterator itr = trackers.begin();
61          itr != trackers.end(); ++itr) {
62       FileTracker tracker;
63       if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
64         NOTREACHED();
65         continue;
66       }
67
68       if (tracker.active())
69         continue;
70
71       FileTracker parent_tracker;
72       bool should_success = metadata_database()->FindTrackerByTrackerID(
73           tracker.parent_tracker_id(), &parent_tracker);
74       if (!should_success) {
75         NOTREACHED();
76         callback.Run(SYNC_STATUS_FAILED);
77         return;
78       }
79       parents_to_remove_.push_back(parent_tracker.file_id());
80     }
81     DetachFromNonPrimaryParents(callback);
82     return;
83   }
84
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) {
91       FileTracker tracker;
92       if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
93         NOTREACHED();
94         continue;
95       }
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()));
99       } else {
100         primary_tracker_id = tracker.tracker_id();
101       }
102     }
103
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);
108
109     RemoveNonPrimaryFiles(callback);
110     return;
111   }
112
113   callback.Run(SYNC_STATUS_NO_CONFLICT);
114 }
115
116 void ConflictResolver::DetachFromNonPrimaryParents(
117     const SyncStatusCallback& callback) {
118   DCHECK(!parents_to_remove_.empty());
119
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(),
128                  callback));
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());
132 }
133
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);
139     return;
140   }
141
142   if (!parents_to_remove_.empty()) {
143     DetachFromNonPrimaryParents(callback);
144     return;
145   }
146
147   callback.Run(SYNC_STATUS_OK);
148 }
149
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) {
154     FileTracker tracker;
155     if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
156       NOTREACHED();
157       continue;
158     }
159
160     scoped_ptr<FileMetadata> file_metadata(new FileMetadata);
161     if (!metadata_database()->FindFileByFileID(
162             tracker.file_id(), file_metadata.get())) {
163       NOTREACHED();
164       continue;
165     }
166
167     if (!primary) {
168       primary = file_metadata.Pass();
169       continue;
170     }
171
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);
176
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();
181         continue;
182       }
183
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();
189         continue;
190       }
191
192       continue;
193     }
194
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.
198       continue;
199     }
200
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();
206       continue;
207     }
208   }
209
210   if (primary)
211     return primary->file_id();
212   return std::string();
213 }
214
215 void ConflictResolver::RemoveNonPrimaryFiles(
216     const SyncStatusCallback& callback) {
217   DCHECK(!non_primary_file_ids_.empty());
218
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();
222
223   DCHECK_NE(target_file_id_, file_id);
224
225   util::Log(logging::LOG_VERBOSE, FROM_HERE,
226             "[ConflictResolver] Remove non-primary file %s", file_id.c_str());
227
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(
231       file_id, etag,
232       base::Bind(&ConflictResolver::DidRemoveFile,
233                  weak_ptr_factory_.GetWeakPtr(),
234                  callback, file_id));
235 }
236
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);
243     return;
244   }
245
246   SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
247   if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
248     callback.Run(status);
249     return;
250   }
251
252   deleted_file_ids_.push_back(file_id);
253   if (!non_primary_file_ids_.empty()) {
254     RemoveNonPrimaryFiles(callback);
255     return;
256   }
257
258   metadata_database()->UpdateByDeletedRemoteFileList(
259       deleted_file_ids_, callback);
260 }
261
262 bool ConflictResolver::IsContextReady() {
263   return sync_context_->GetDriveService() &&
264       sync_context_->GetMetadataDatabase();
265 }
266
267 void ConflictResolver::UpdateFileMetadata(
268     const std::string& file_id,
269     const SyncStatusCallback& callback) {
270   drive_service()->GetResourceEntry(
271       file_id,
272       base::Bind(&ConflictResolver::DidGetRemoteMetadata,
273                  weak_ptr_factory_.GetWeakPtr(), file_id, callback));
274 }
275
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);
284     return;
285   }
286
287   if (error != google_apis::HTTP_NOT_FOUND) {
288     metadata_database()->UpdateByDeletedRemoteFile(file_id, callback);
289     return;
290   }
291
292   if (!entry) {
293     NOTREACHED();
294     callback.Run(SYNC_STATUS_FAILED);
295     return;
296   }
297
298   metadata_database()->UpdateByFileResource(
299       *drive::util::ConvertResourceEntryToFileResource(*entry),
300       callback);
301 }
302
303 drive::DriveServiceInterface* ConflictResolver::drive_service() {
304   set_used_network(true);
305   return sync_context_->GetDriveService();
306 }
307
308 MetadataDatabase* ConflictResolver::metadata_database() {
309   return sync_context_->GetMetadataDatabase();
310 }
311
312 }  // namespace drive_backend
313 }  // namespace sync_file_system