conflict_resolver.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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/location.h"
9#include "base/logging.h"
10#include "chrome/browser/drive/drive_api_util.h"
11#include "chrome/browser/drive/drive_service_interface.h"
12#include "chrome/browser/drive/drive_uploader.h"
13#include "chrome/browser/sync_file_system/drive_backend/drive_backend_util.h"
14#include "chrome/browser/sync_file_system/drive_backend/metadata_database.h"
15#include "chrome/browser/sync_file_system/drive_backend/metadata_database.pb.h"
16#include "chrome/browser/sync_file_system/drive_backend/sync_engine_context.h"
17#include "chrome/browser/sync_file_system/drive_backend_v1/drive_file_sync_util.h"
18#include "google_apis/drive/drive_api_parser.h"
19
20namespace sync_file_system {
21namespace drive_backend {
22
23ConflictResolver::ConflictResolver(SyncEngineContext* sync_context)
24    : sync_context_(sync_context),
25      weak_ptr_factory_(this) {
26}
27
28ConflictResolver::~ConflictResolver() {
29}
30
31void ConflictResolver::Run(const SyncStatusCallback& callback) {
32  if (!IsContextReady()) {
33    NOTREACHED();
34    callback.Run(SYNC_STATUS_FAILED);
35    return;
36  }
37
38  // Conflict resolution should be invoked on clean tree.
39  if (metadata_database()->GetNormalPriorityDirtyTracker(NULL) ||
40      metadata_database()->GetLowPriorityDirtyTracker(NULL)) {
41    NOTREACHED();
42    callback.Run(SYNC_STATUS_FAILED);
43    return;
44  }
45
46  TrackerSet trackers;
47  if (metadata_database()->GetMultiParentFileTrackers(
48          &target_file_id_, &trackers)) {
49    DCHECK_LT(1u, trackers.size());
50    if (!trackers.has_active()) {
51      NOTREACHED();
52      callback.Run(SYNC_STATUS_FAILED);
53      return;
54    }
55
56    DCHECK(trackers.has_active());
57    for (TrackerSet::const_iterator itr = trackers.begin();
58         itr != trackers.end(); ++itr) {
59      const FileTracker& tracker = **itr;
60      if (tracker.active())
61        continue;
62
63      FileTracker parent_tracker;
64      bool should_success = metadata_database()->FindTrackerByTrackerID(
65          tracker.parent_tracker_id(), &parent_tracker);
66      if (!should_success) {
67        NOTREACHED();
68        callback.Run(SYNC_STATUS_FAILED);
69        return;
70      }
71      parents_to_remove_.push_back(parent_tracker.file_id());
72    }
73    DetachFromNonPrimaryParents(callback);
74    return;
75  }
76
77  if (metadata_database()->GetConflictingTrackers(&trackers)) {
78    target_file_id_ = PickPrimaryFile(trackers);
79    DCHECK(!target_file_id_.empty());
80    for (TrackerSet::const_iterator itr = trackers.begin();
81         itr != trackers.end(); ++itr) {
82      const FileTracker& tracker = **itr;
83      if (tracker.file_id() != target_file_id_) {
84        non_primary_file_ids_.push_back(
85            std::make_pair(tracker.file_id(), tracker.synced_details().etag()));
86      }
87    }
88    RemoveNonPrimaryFiles(callback);
89    return;
90  }
91
92  callback.Run(SYNC_STATUS_NO_CONFLICT);
93}
94
95void ConflictResolver::DetachFromNonPrimaryParents(
96    const SyncStatusCallback& callback) {
97  DCHECK(!parents_to_remove_.empty());
98
99  // TODO(tzik): Check if ETag match is available for
100  // RemoteResourceFromDirectory.
101  std::string parent_folder_id = parents_to_remove_.back();
102  parents_to_remove_.pop_back();
103  drive_service()->RemoveResourceFromDirectory(
104      parent_folder_id, target_file_id_,
105      base::Bind(&ConflictResolver::DidDetachFromParent,
106                 weak_ptr_factory_.GetWeakPtr(),
107                 callback));
108}
109
110void ConflictResolver::DidDetachFromParent(const SyncStatusCallback& callback,
111                                           google_apis::GDataErrorCode error) {
112  SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
113  if (status != SYNC_STATUS_OK) {
114    callback.Run(status);
115    return;
116  }
117
118  if (!parents_to_remove_.empty()) {
119    DetachFromNonPrimaryParents(callback);
120    return;
121  }
122
123  callback.Run(SYNC_STATUS_OK);
124}
125
126std::string ConflictResolver::PickPrimaryFile(const TrackerSet& trackers) {
127  scoped_ptr<FileMetadata> primary;
128  for (TrackerSet::const_iterator itr = trackers.begin();
129       itr != trackers.end(); ++itr) {
130    const FileTracker& tracker = **itr;
131    scoped_ptr<FileMetadata> file_metadata(new FileMetadata);
132    bool should_success = metadata_database()->FindFileByFileID(
133        tracker.file_id(), file_metadata.get());
134    if (!should_success) {
135      NOTREACHED();
136      continue;
137    }
138
139    if (!primary) {
140      primary = file_metadata.Pass();
141      continue;
142    }
143
144    DCHECK(primary->details().file_kind() == FILE_KIND_FILE ||
145           primary->details().file_kind() == FILE_KIND_FOLDER);
146    DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE ||
147           file_metadata->details().file_kind() == FILE_KIND_FOLDER);
148
149    if (primary->details().file_kind() == FILE_KIND_FILE) {
150      if (file_metadata->details().file_kind() == FILE_KIND_FOLDER) {
151        // Prioritize folders over regular files.
152        primary = file_metadata.Pass();
153        continue;
154      }
155
156      DCHECK(file_metadata->details().file_kind() == FILE_KIND_FILE);
157      if (primary->details().modification_time() <
158          file_metadata->details().modification_time()) {
159        // Prioritize last write for regular files.
160        primary = file_metadata.Pass();
161        continue;
162      }
163
164      continue;
165    }
166
167    DCHECK(primary->details().file_kind() == FILE_KIND_FOLDER);
168    if (file_metadata->details().file_kind() == FILE_KIND_FILE) {
169      // Prioritize folders over regular files.
170      continue;
171    }
172
173    DCHECK(file_metadata->details().file_kind() == FILE_KIND_FOLDER);
174    if (primary->details().creation_time() >
175        file_metadata->details().creation_time()) {
176      // Prioritize first create for folders.
177      primary = file_metadata.Pass();
178      continue;
179    }
180  }
181
182  if (primary)
183    return primary->file_id();
184  return std::string();
185}
186
187void ConflictResolver::RemoveNonPrimaryFiles(
188    const SyncStatusCallback& callback) {
189  DCHECK(!non_primary_file_ids_.empty());
190
191  std::string file_id = non_primary_file_ids_.back().first;
192  std::string etag = non_primary_file_ids_.back().second;
193  non_primary_file_ids_.pop_back();
194  DCHECK_NE(target_file_id_, file_id);
195
196  // TODO(tzik): Check if the file is a folder, and merge its contents into
197  // the folder identified by |target_file_id_|.
198  drive_service()->DeleteResource(
199      file_id, etag,
200      base::Bind(&ConflictResolver::DidRemoveFile,
201                 weak_ptr_factory_.GetWeakPtr(),
202                 callback, file_id));
203}
204
205void ConflictResolver::DidRemoveFile(const SyncStatusCallback& callback,
206                                     const std::string& file_id,
207                                     google_apis::GDataErrorCode error) {
208  if (error == google_apis::HTTP_PRECONDITION ||
209      error == google_apis::HTTP_CONFLICT) {
210    callback.Run(SYNC_STATUS_RETRY);
211    return;
212  }
213
214  SyncStatusCode status = GDataErrorCodeToSyncStatusCode(error);
215  if (status != SYNC_STATUS_OK && error != google_apis::HTTP_NOT_FOUND) {
216    callback.Run(status);
217    return;
218  }
219
220  if (!non_primary_file_ids_.empty()) {
221    RemoveNonPrimaryFiles(callback);
222    return;
223  }
224
225  callback.Run(SYNC_STATUS_OK);
226}
227
228bool ConflictResolver::IsContextReady() {
229  return sync_context_->GetDriveService() &&
230      sync_context_->GetMetadataDatabase();
231}
232
233drive::DriveServiceInterface* ConflictResolver::drive_service() {
234  set_used_network(true);
235  return sync_context_->GetDriveService();
236}
237
238MetadataDatabase* ConflictResolver::metadata_database() {
239  return sync_context_->GetMetadataDatabase();
240}
241
242}  // namespace drive_backend
243}  // namespace sync_file_system
244