conflict_resolver.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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
24namespace sync_file_system {
25namespace drive_backend {
26
27ConflictResolver::ConflictResolver(SyncEngineContext* sync_context)
28    : sync_context_(sync_context),
29      weak_ptr_factory_(this) {}
30
31ConflictResolver::~ConflictResolver() {}
32
33void ConflictResolver::RunPreflight(scoped_ptr<SyncTaskToken> token) {
34  token->InitializeTaskLog("Conflict Resolution");
35
36  scoped_ptr<BlockingFactor> blocking_factor(new BlockingFactor);
37  blocking_factor->exclusive = true;
38  SyncTaskManager::UpdateBlockingFactor(
39      token.Pass(), blocking_factor.Pass(),
40      base::Bind(&ConflictResolver::RunExclusive,
41                 weak_ptr_factory_.GetWeakPtr()));
42}
43
44void ConflictResolver::RunExclusive(scoped_ptr<SyncTaskToken> token) {
45  if (!IsContextReady()) {
46    NOTREACHED();
47    SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
48    return;
49  }
50
51  // Conflict resolution should be invoked on clean tree.
52  if (metadata_database()->HasDirtyTracker()) {
53    NOTREACHED();
54    SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
55    return;
56  }
57
58  TrackerIDSet trackers;
59  if (metadata_database()->GetMultiParentFileTrackers(
60          &target_file_id_, &trackers)) {
61    DCHECK_LT(1u, trackers.size());
62    if (!trackers.has_active()) {
63      NOTREACHED();
64      SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
65      return;
66    }
67
68    token->RecordLog(base::StringPrintf(
69        "Detected multi-parent trackers (active tracker_id=%" PRId64 ")",
70        trackers.active_tracker()));
71
72    DCHECK(trackers.has_active());
73    for (TrackerIDSet::const_iterator itr = trackers.begin();
74         itr != trackers.end(); ++itr) {
75      FileTracker tracker;
76      if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
77        NOTREACHED();
78        continue;
79      }
80
81      if (tracker.active())
82        continue;
83
84      FileTracker parent_tracker;
85      bool should_success = metadata_database()->FindTrackerByTrackerID(
86          tracker.parent_tracker_id(), &parent_tracker);
87      if (!should_success) {
88        NOTREACHED();
89        SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
90        return;
91      }
92      parents_to_remove_.push_back(parent_tracker.file_id());
93    }
94    DetachFromNonPrimaryParents(token.Pass());
95    return;
96  }
97
98  if (metadata_database()->GetConflictingTrackers(&trackers)) {
99    target_file_id_ = PickPrimaryFile(trackers);
100    DCHECK(!target_file_id_.empty());
101    int64 primary_tracker_id = -1;
102    for (TrackerIDSet::const_iterator itr = trackers.begin();
103         itr != trackers.end(); ++itr) {
104      FileTracker tracker;
105      if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
106        NOTREACHED();
107        continue;
108      }
109      if (tracker.file_id() != target_file_id_) {
110        non_primary_file_ids_.push_back(
111            std::make_pair(tracker.file_id(), tracker.synced_details().etag()));
112      } else {
113        primary_tracker_id = tracker.tracker_id();
114      }
115    }
116
117    token->RecordLog(base::StringPrintf(
118        "Detected %" PRIuS " conflicting trackers "
119        "(primary tracker_id=%" PRId64 ")",
120        non_primary_file_ids_.size(), primary_tracker_id));
121
122    RemoveNonPrimaryFiles(token.Pass());
123    return;
124  }
125
126  SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_NO_CONFLICT);
127}
128
129void ConflictResolver::DetachFromNonPrimaryParents(
130    scoped_ptr<SyncTaskToken> token) {
131  DCHECK(!parents_to_remove_.empty());
132
133  // TODO(tzik): Check if ETag match is available for
134  // RemoteResourceFromDirectory.
135  std::string parent_folder_id = parents_to_remove_.back();
136  parents_to_remove_.pop_back();
137
138  token->RecordLog(base::StringPrintf(
139      "Detach %s from %s",
140      target_file_id_.c_str(), parent_folder_id.c_str()));
141
142  drive_service()->RemoveResourceFromDirectory(
143      parent_folder_id, target_file_id_,
144      base::Bind(&ConflictResolver::DidDetachFromParent,
145                 weak_ptr_factory_.GetWeakPtr(),
146                 base::Passed(&token)));
147}
148
149void ConflictResolver::DidDetachFromParent(scoped_ptr<SyncTaskToken> token,
150                                           google_apis::GDataErrorCode error) {
151  SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
152  if (status != SYNC_STATUS_OK) {
153    SyncTaskManager::NotifyTaskDone(token.Pass(), status);
154    return;
155  }
156
157  if (!parents_to_remove_.empty()) {
158    DetachFromNonPrimaryParents(token.Pass());
159    return;
160  }
161
162  SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_OK);
163}
164
165std::string ConflictResolver::PickPrimaryFile(const TrackerIDSet& trackers) {
166  scoped_ptr<FileMetadata> primary;
167  for (TrackerIDSet::const_iterator itr = trackers.begin();
168       itr != trackers.end(); ++itr) {
169    FileTracker tracker;
170    if (!metadata_database()->FindTrackerByTrackerID(*itr, &tracker)) {
171      NOTREACHED();
172      continue;
173    }
174
175    scoped_ptr<FileMetadata> file_metadata(new FileMetadata);
176    if (!metadata_database()->FindFileByFileID(
177            tracker.file_id(), file_metadata.get())) {
178      NOTREACHED();
179      continue;
180    }
181
182    if (!primary) {
183      primary = file_metadata.Pass();
184      continue;
185    }
186
187    DCHECK(primary->details().file_kind() == FILE_KIND_FILE ||
188           primary->details().file_kind() == FILE_KIND_FOLDER);
189    DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE ||
190           file_metadata->details().file_kind() == FILE_KIND_FOLDER);
191
192    if (primary->details().file_kind() == FILE_KIND_FILE) {
193      if (file_metadata->details().file_kind() == FILE_KIND_FOLDER) {
194        // Prioritize folders over regular files.
195        primary = file_metadata.Pass();
196        continue;
197      }
198
199      DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE);
200      if (primary->details().modification_time() <
201          file_metadata->details().modification_time()) {
202        // Prioritize last write for regular files.
203        primary = file_metadata.Pass();
204        continue;
205      }
206
207      continue;
208    }
209
210    DCHECK(primary->details().file_kind() == FILE_KIND_FOLDER);
211    if (file_metadata->details().file_kind() == FILE_KIND_FILE) {
212      // Prioritize folders over regular files.
213      continue;
214    }
215
216    DCHECK(file_metadata->details().file_kind() == FILE_KIND_FOLDER);
217    if (primary->details().creation_time() >
218        file_metadata->details().creation_time()) {
219      // Prioritize first create for folders.
220      primary = file_metadata.Pass();
221      continue;
222    }
223  }
224
225  if (primary)
226    return primary->file_id();
227  return std::string();
228}
229
230void ConflictResolver::RemoveNonPrimaryFiles(scoped_ptr<SyncTaskToken> token) {
231  DCHECK(!non_primary_file_ids_.empty());
232
233  std::string file_id = non_primary_file_ids_.back().first;
234  std::string etag = non_primary_file_ids_.back().second;
235  non_primary_file_ids_.pop_back();
236
237  DCHECK_NE(target_file_id_, file_id);
238
239  token->RecordLog(base::StringPrintf(
240      "Remove non-primary file %s", file_id.c_str()));
241
242  // TODO(tzik): Check if the file is a folder, and merge its contents into
243  // the folder identified by |target_file_id_|.
244  drive_service()->DeleteResource(
245      file_id, etag,
246      base::Bind(&ConflictResolver::DidRemoveFile,
247                 weak_ptr_factory_.GetWeakPtr(),
248                 base::Passed(&token), file_id));
249}
250
251void ConflictResolver::DidRemoveFile(scoped_ptr<SyncTaskToken> token,
252                                     const std::string& file_id,
253                                     google_apis::GDataErrorCode error) {
254  if (error == google_apis::HTTP_PRECONDITION ||
255      error == google_apis::HTTP_CONFLICT) {
256    UpdateFileMetadata(file_id, token.Pass());
257    return;
258  }
259
260  SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
261  if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
262    SyncTaskManager::NotifyTaskDone(token.Pass(), status);
263    return;
264  }
265
266  deleted_file_ids_.push_back(file_id);
267  if (!non_primary_file_ids_.empty()) {
268    RemoveNonPrimaryFiles(token.Pass());
269    return;
270  }
271
272  metadata_database()->UpdateByDeletedRemoteFileList(
273      deleted_file_ids_, SyncTaskToken::WrapToCallback(token.Pass()));
274}
275
276bool ConflictResolver::IsContextReady() {
277  return sync_context_->GetDriveService() &&
278      sync_context_->GetMetadataDatabase();
279}
280
281void 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
291void 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    metadata_database()->UpdateByDeletedRemoteFile(
304        file_id, SyncTaskToken::WrapToCallback(token.Pass()));
305    return;
306  }
307
308  if (!entry) {
309    NOTREACHED();
310    SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
311    return;
312  }
313
314  metadata_database()->UpdateByFileResource(
315      *entry, SyncTaskToken::WrapToCallback(token.Pass()));
316}
317
318drive::DriveServiceInterface* ConflictResolver::drive_service() {
319  set_used_network(true);
320  return sync_context_->GetDriveService();
321}
322
323MetadataDatabase* ConflictResolver::metadata_database() {
324  return sync_context_->GetMetadataDatabase();
325}
326
327}  // namespace drive_backend
328}  // namespace sync_file_system
329