conflict_resolver.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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    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
128void 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
148void 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
164std::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
229void 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
250void 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  metadata_database()->UpdateByDeletedRemoteFileList(
272      deleted_file_ids_, SyncTaskToken::WrapToCallback(token.Pass()));
273}
274
275bool ConflictResolver::IsContextReady() {
276  return sync_context_->GetDriveService() &&
277      sync_context_->GetMetadataDatabase();
278}
279
280void ConflictResolver::UpdateFileMetadata(
281    const std::string& file_id,
282    scoped_ptr<SyncTaskToken> token) {
283  drive_service()->GetFileResource(
284      file_id,
285      base::Bind(&ConflictResolver::DidGetRemoteMetadata,
286                 weak_ptr_factory_.GetWeakPtr(), file_id,
287                 base::Passed(&token)));
288}
289
290void ConflictResolver::DidGetRemoteMetadata(
291    const std::string& file_id,
292    scoped_ptr<SyncTaskToken> token,
293    google_apis::GDataErrorCode error,
294    scoped_ptr<google_apis::FileResource> entry) {
295  SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
296  if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
297    SyncTaskManager::NotifyTaskDone(token.Pass(), status);
298    return;
299  }
300
301  if (error != google_apis::HTTP_NOT_FOUND) {
302    metadata_database()->UpdateByDeletedRemoteFile(
303        file_id, SyncTaskToken::WrapToCallback(token.Pass()));
304    return;
305  }
306
307  if (!entry) {
308    NOTREACHED();
309    SyncTaskManager::NotifyTaskDone(token.Pass(), SYNC_STATUS_FAILED);
310    return;
311  }
312
313  metadata_database()->UpdateByFileResource(
314      *entry, SyncTaskToken::WrapToCallback(token.Pass()));
315}
316
317drive::DriveServiceInterface* ConflictResolver::drive_service() {
318  set_used_network(true);
319  return sync_context_->GetDriveService();
320}
321
322MetadataDatabase* ConflictResolver::metadata_database() {
323  return sync_context_->GetMetadataDatabase();
324}
325
326}  // namespace drive_backend
327}  // namespace sync_file_system
328