Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / drive / change_list_processor.cc
blob79aab646f3414c19a139fb145fe9d2deb19e1a39
1 // Copyright (c) 2012 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.
5 #include "components/drive/change_list_processor.h"
7 #include "base/metrics/histogram.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/synchronization/cancellation_flag.h"
10 #include "components/drive/drive.pb.h"
11 #include "components/drive/drive_api_util.h"
12 #include "components/drive/file_change.h"
13 #include "components/drive/file_system_core_util.h"
14 #include "components/drive/resource_entry_conversion.h"
15 #include "components/drive/resource_metadata.h"
16 #include "google_apis/drive/drive_api_parser.h"
18 namespace drive {
19 namespace internal {
21 namespace {
23 class ChangeListToEntryMapUMAStats {
24 public:
25 ChangeListToEntryMapUMAStats()
26 : num_regular_files_(0),
27 num_hosted_documents_(0),
28 num_shared_with_me_entries_(0) {
31 // Increments number of files.
32 void IncrementNumFiles(bool is_hosted_document) {
33 is_hosted_document ? num_hosted_documents_++ : num_regular_files_++;
36 // Increments number of shared-with-me entries.
37 void IncrementNumSharedWithMeEntries() {
38 num_shared_with_me_entries_++;
41 // Updates UMA histograms with file counts.
42 void UpdateFileCountUmaHistograms() {
43 const int num_total_files = num_hosted_documents_ + num_regular_files_;
44 UMA_HISTOGRAM_COUNTS("Drive.NumberOfRegularFiles", num_regular_files_);
45 UMA_HISTOGRAM_COUNTS("Drive.NumberOfHostedDocuments",
46 num_hosted_documents_);
47 UMA_HISTOGRAM_COUNTS("Drive.NumberOfTotalFiles", num_total_files);
48 UMA_HISTOGRAM_COUNTS("Drive.NumberOfSharedWithMeEntries",
49 num_shared_with_me_entries_);
52 private:
53 int num_regular_files_;
54 int num_hosted_documents_;
55 int num_shared_with_me_entries_;
58 // Returns true if it's OK to overwrite the local entry with the remote one.
59 bool ShouldApplyChange(const ResourceEntry& local_entry,
60 const ResourceEntry& remote_entry) {
61 if (local_entry.metadata_edit_state() == ResourceEntry::CLEAN)
62 return true;
63 return base::Time::FromInternalValue(remote_entry.modification_date()) >
64 base::Time::FromInternalValue(local_entry.modification_date());
67 } // namespace
69 std::string DirectoryFetchInfo::ToString() const {
70 return ("local_id: " + local_id_ +
71 ", resource_id: " + resource_id_ +
72 ", changestamp: " + base::Int64ToString(changestamp_));
75 ChangeList::ChangeList() {}
77 ChangeList::ChangeList(const google_apis::ChangeList& change_list)
78 : next_url_(change_list.next_link()),
79 largest_changestamp_(change_list.largest_change_id()) {
80 const ScopedVector<google_apis::ChangeResource>& items = change_list.items();
81 entries_.resize(items.size());
82 parent_resource_ids_.resize(items.size());
83 size_t entries_index = 0;
84 for (size_t i = 0; i < items.size(); ++i) {
85 if (ConvertChangeResourceToResourceEntry(
86 *items[i],
87 &entries_[entries_index],
88 &parent_resource_ids_[entries_index])) {
89 ++entries_index;
92 entries_.resize(entries_index);
93 parent_resource_ids_.resize(entries_index);
96 ChangeList::ChangeList(const google_apis::FileList& file_list)
97 : next_url_(file_list.next_link()),
98 largest_changestamp_(0) {
99 const ScopedVector<google_apis::FileResource>& items = file_list.items();
100 entries_.resize(items.size());
101 parent_resource_ids_.resize(items.size());
102 size_t entries_index = 0;
103 for (size_t i = 0; i < items.size(); ++i) {
104 if (ConvertFileResourceToResourceEntry(
105 *items[i],
106 &entries_[entries_index],
107 &parent_resource_ids_[entries_index])) {
108 ++entries_index;
111 entries_.resize(entries_index);
112 parent_resource_ids_.resize(entries_index);
115 ChangeList::~ChangeList() {}
117 ChangeListProcessor::ChangeListProcessor(ResourceMetadata* resource_metadata,
118 base::CancellationFlag* in_shutdown)
119 : resource_metadata_(resource_metadata),
120 in_shutdown_(in_shutdown),
121 changed_files_(new FileChange) {
124 ChangeListProcessor::~ChangeListProcessor() {
127 FileError ChangeListProcessor::Apply(
128 scoped_ptr<google_apis::AboutResource> about_resource,
129 ScopedVector<ChangeList> change_lists,
130 bool is_delta_update) {
131 DCHECK(about_resource);
133 int64 largest_changestamp = 0;
134 if (is_delta_update) {
135 if (!change_lists.empty()) {
136 // The changestamp appears in the first page of the change list.
137 // The changestamp does not appear in the full resource list.
138 largest_changestamp = change_lists[0]->largest_changestamp();
139 DCHECK_GE(change_lists[0]->largest_changestamp(), 0);
141 } else {
142 largest_changestamp = about_resource->largest_change_id();
144 DVLOG(1) << "Root folder ID is " << about_resource->root_folder_id();
145 DCHECK(!about_resource->root_folder_id().empty());
148 // Convert ChangeList to map.
149 ChangeListToEntryMapUMAStats uma_stats;
150 for (size_t i = 0; i < change_lists.size(); ++i) {
151 ChangeList* change_list = change_lists[i];
153 std::vector<ResourceEntry>* entries = change_list->mutable_entries();
154 for (size_t i = 0; i < entries->size(); ++i) {
155 ResourceEntry* entry = &(*entries)[i];
157 // Count the number of files.
158 if (!entry->file_info().is_directory()) {
159 uma_stats.IncrementNumFiles(
160 entry->file_specific_info().is_hosted_document());
161 if (entry->shared_with_me())
162 uma_stats.IncrementNumSharedWithMeEntries();
164 parent_resource_id_map_[entry->resource_id()] =
165 change_list->parent_resource_ids()[i];
166 entry_map_[entry->resource_id()].Swap(entry);
167 LOG_IF(WARNING, !entry->resource_id().empty())
168 << "Found duplicated file: " << entry->base_name();
172 // Add the largest changestamp for directories.
173 for (ResourceEntryMap::iterator it = entry_map_.begin();
174 it != entry_map_.end(); ++it) {
175 if (it->second.file_info().is_directory()) {
176 it->second.mutable_directory_specific_info()->set_changestamp(
177 largest_changestamp);
181 FileError error = ApplyEntryMap(largest_changestamp, about_resource.Pass());
182 if (error != FILE_ERROR_OK) {
183 DLOG(ERROR) << "ApplyEntryMap failed: " << FileErrorToString(error);
184 return error;
187 // Update changestamp.
188 error = resource_metadata_->SetLargestChangestamp(largest_changestamp);
189 if (error != FILE_ERROR_OK) {
190 DLOG(ERROR) << "SetLargestChangeStamp failed: " << FileErrorToString(error);
191 return error;
194 // Shouldn't record histograms when processing delta update.
195 if (!is_delta_update)
196 uma_stats.UpdateFileCountUmaHistograms();
198 return FILE_ERROR_OK;
201 FileError ChangeListProcessor::ApplyEntryMap(
202 int64 changestamp,
203 scoped_ptr<google_apis::AboutResource> about_resource) {
204 DCHECK(about_resource);
206 // Create the entry for "My Drive" directory with the latest changestamp.
207 ResourceEntry root;
208 FileError error = resource_metadata_->GetResourceEntryByPath(
209 util::GetDriveMyDriveRootPath(), &root);
210 if (error != FILE_ERROR_OK) {
211 LOG(ERROR) << "Failed to get root entry: " << FileErrorToString(error);
212 return error;
215 root.mutable_directory_specific_info()->set_changestamp(changestamp);
216 root.set_resource_id(about_resource->root_folder_id());
217 error = resource_metadata_->RefreshEntry(root);
218 if (error != FILE_ERROR_OK) {
219 LOG(ERROR) << "Failed to update root entry: " << FileErrorToString(error);
220 return error;
223 // Gather the set of changes in the old path.
224 // Note that we want to notify the change in both old and new paths (suppose
225 // /a/b/c is moved to /x/y/c. We want to notify both "/a/b" and "/x/y".)
226 // The old paths must be calculated before we apply any actual changes.
227 // The new paths are calculated after each change is applied. It correctly
228 // sets the new path because we apply changes in such an order (see below).
229 for (ResourceEntryMap::iterator it = entry_map_.begin();
230 it != entry_map_.end(); ++it) {
231 UpdateChangedDirs(it->second);
234 // Apply all entries except deleted ones to the metadata.
235 std::vector<std::string> deleted_resource_ids;
236 while (!entry_map_.empty()) {
237 if (in_shutdown_ && in_shutdown_->IsSet())
238 return FILE_ERROR_ABORT;
240 ResourceEntryMap::iterator it = entry_map_.begin();
242 // Process deleted entries later to avoid deleting moved entries under it.
243 if (it->second.deleted()) {
244 deleted_resource_ids.push_back(it->first);
245 entry_map_.erase(it);
246 continue;
249 // Start from entry_map_.begin() and traverse ancestors using the
250 // parent-child relationships in the result (after this apply) tree.
251 // Then apply the topmost change first.
253 // By doing this, assuming the result tree does not contain any cycles, we
254 // can guarantee that no cycle is made during this apply (i.e. no entry gets
255 // moved under any of its descendants) because the following conditions are
256 // always satisfied in any move:
257 // - The new parent entry is not a descendant of the moved entry.
258 // - The new parent and its ancestors will no longer move during this apply.
259 std::vector<ResourceEntryMap::iterator> entries;
260 for (ResourceEntryMap::iterator it = entry_map_.begin();
261 it != entry_map_.end();) {
262 entries.push_back(it);
264 DCHECK(parent_resource_id_map_.count(it->first)) << it->first;
265 const std::string& parent_resource_id =
266 parent_resource_id_map_[it->first];
268 if (parent_resource_id.empty()) // This entry has no parent.
269 break;
271 ResourceEntryMap::iterator it_parent =
272 entry_map_.find(parent_resource_id);
273 if (it_parent == entry_map_.end()) {
274 // Current entry's parent is already updated or not going to be updated,
275 // get the parent from the local tree.
276 std::string parent_local_id;
277 FileError error = resource_metadata_->GetIdByResourceId(
278 parent_resource_id, &parent_local_id);
279 if (error != FILE_ERROR_OK) {
280 // See crbug.com/326043. In some complicated situations, parent folder
281 // for shared entries may be accessible (and hence its resource id is
282 // included), but not in the change/file list.
283 // In such a case, clear the parent and move it to drive/other.
284 if (error == FILE_ERROR_NOT_FOUND) {
285 parent_resource_id_map_[it->first] = "";
286 } else {
287 LOG(ERROR) << "Failed to get local ID: " << parent_resource_id
288 << ", error = " << FileErrorToString(error);
290 break;
292 ResourceEntry parent_entry;
293 while (it_parent == entry_map_.end() && !parent_local_id.empty()) {
294 error = resource_metadata_->GetResourceEntryById(
295 parent_local_id, &parent_entry);
296 if (error != FILE_ERROR_OK) {
297 LOG(ERROR) << "Failed to get local entry: "
298 << FileErrorToString(error);
299 break;
301 it_parent = entry_map_.find(parent_entry.resource_id());
302 parent_local_id = parent_entry.parent_local_id();
305 it = it_parent;
308 // Apply the parent first.
309 std::reverse(entries.begin(), entries.end());
310 for (size_t i = 0; i < entries.size(); ++i) {
311 // Skip root entry in the change list. We don't expect servers to send
312 // root entry, but we should better be defensive (see crbug.com/297259).
313 ResourceEntryMap::iterator it = entries[i];
314 if (it->first != root.resource_id()) {
315 FileError error = ApplyEntry(it->second);
316 if (error != FILE_ERROR_OK) {
317 LOG(ERROR) << "ApplyEntry failed: " << FileErrorToString(error)
318 << ", title = " << it->second.title();
319 return error;
322 entry_map_.erase(it);
326 // Apply deleted entries.
327 for (size_t i = 0; i < deleted_resource_ids.size(); ++i) {
328 std::string local_id;
329 FileError error = resource_metadata_->GetIdByResourceId(
330 deleted_resource_ids[i], &local_id);
331 switch (error) {
332 case FILE_ERROR_OK:
333 error = resource_metadata_->RemoveEntry(local_id);
334 break;
335 case FILE_ERROR_NOT_FOUND:
336 error = FILE_ERROR_OK;
337 break;
338 default:
339 break;
341 if (error != FILE_ERROR_OK) {
342 LOG(ERROR) << "Failed to delete: " << FileErrorToString(error)
343 << ", resource_id = " << deleted_resource_ids[i];
344 return error;
348 return FILE_ERROR_OK;
351 FileError ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) {
352 DCHECK(!entry.deleted());
353 DCHECK(parent_resource_id_map_.count(entry.resource_id()));
354 const std::string& parent_resource_id =
355 parent_resource_id_map_[entry.resource_id()];
357 ResourceEntry new_entry(entry);
358 FileError error = SetParentLocalIdOfEntry(resource_metadata_, &new_entry,
359 parent_resource_id);
360 if (error != FILE_ERROR_OK)
361 return error;
363 // Lookup the entry.
364 std::string local_id;
365 error = resource_metadata_->GetIdByResourceId(entry.resource_id(), &local_id);
367 ResourceEntry existing_entry;
368 if (error == FILE_ERROR_OK)
369 error = resource_metadata_->GetResourceEntryById(local_id, &existing_entry);
371 switch (error) {
372 case FILE_ERROR_OK:
373 if (ShouldApplyChange(existing_entry, new_entry)) {
374 // Entry exists and needs to be refreshed.
375 new_entry.set_local_id(local_id);
376 // Keep the to-be-synced properties of the existing resource entry.
377 new_entry.mutable_new_properties()->CopyFrom(
378 existing_entry.new_properties());
379 error = resource_metadata_->RefreshEntry(new_entry);
380 } else {
381 if (entry.file_info().is_directory()) {
382 // No need to refresh, but update the changestamp.
383 new_entry = existing_entry;
384 new_entry.mutable_directory_specific_info()->set_changestamp(
385 new_entry.directory_specific_info().changestamp());
386 error = resource_metadata_->RefreshEntry(new_entry);
388 DVLOG(1) << "Change was discarded for: " << entry.resource_id();
390 break;
391 case FILE_ERROR_NOT_FOUND: { // Adding a new entry.
392 std::string local_id;
393 error = resource_metadata_->AddEntry(new_entry, &local_id);
394 break;
396 default:
397 return error;
399 if (error != FILE_ERROR_OK)
400 return error;
402 UpdateChangedDirs(entry);
403 return FILE_ERROR_OK;
406 // static
407 FileError ChangeListProcessor::RefreshDirectory(
408 ResourceMetadata* resource_metadata,
409 const DirectoryFetchInfo& directory_fetch_info,
410 scoped_ptr<ChangeList> change_list,
411 std::vector<ResourceEntry>* out_refreshed_entries) {
412 DCHECK(!directory_fetch_info.empty());
414 ResourceEntry directory;
415 FileError error = resource_metadata->GetResourceEntryById(
416 directory_fetch_info.local_id(), &directory);
417 if (error != FILE_ERROR_OK)
418 return error;
420 if (!directory.file_info().is_directory())
421 return FILE_ERROR_NOT_A_DIRECTORY;
423 std::vector<ResourceEntry>* entries = change_list->mutable_entries();
424 for (size_t i = 0; i < entries->size(); ++i) {
425 ResourceEntry* entry = &(*entries)[i];
426 const std::string& parent_resource_id =
427 change_list->parent_resource_ids()[i];
429 // Skip if the parent resource ID does not match. This is needed to
430 // handle entries with multiple parents. For such entries, the first
431 // parent is picked and other parents are ignored, hence some entries may
432 // have a parent resource ID which does not match the target directory's.
433 if (parent_resource_id != directory_fetch_info.resource_id()) {
434 DVLOG(1) << "Wrong-parent entry rejected: " << entry->resource_id();
435 continue;
438 entry->set_parent_local_id(directory_fetch_info.local_id());
440 std::string local_id;
441 error = resource_metadata->GetIdByResourceId(entry->resource_id(),
442 &local_id);
443 if (error == FILE_ERROR_OK) {
444 entry->set_local_id(local_id);
445 error = resource_metadata->RefreshEntry(*entry);
448 if (error == FILE_ERROR_NOT_FOUND) { // If refreshing fails, try adding.
449 entry->clear_local_id();
450 error = resource_metadata->AddEntry(*entry, &local_id);
453 if (error != FILE_ERROR_OK)
454 return error;
456 ResourceEntry result_entry;
457 error = resource_metadata->GetResourceEntryById(local_id, &result_entry);
458 if (error != FILE_ERROR_OK)
459 return error;
460 out_refreshed_entries->push_back(result_entry);
462 return FILE_ERROR_OK;
465 // static
466 FileError ChangeListProcessor::SetParentLocalIdOfEntry(
467 ResourceMetadata* resource_metadata,
468 ResourceEntry* entry,
469 const std::string& parent_resource_id) {
470 std::string parent_local_id;
471 if (parent_resource_id.empty()) {
472 // Entries without parents should go under "other" directory.
473 parent_local_id = util::kDriveOtherDirLocalId;
474 } else {
475 FileError error = resource_metadata->GetIdByResourceId(
476 parent_resource_id, &parent_local_id);
477 if (error != FILE_ERROR_OK)
478 return error;
480 entry->set_parent_local_id(parent_local_id);
481 return FILE_ERROR_OK;
484 void ChangeListProcessor::UpdateChangedDirs(const ResourceEntry& entry) {
485 DCHECK(!entry.resource_id().empty());
487 std::string local_id;
488 base::FilePath file_path;
489 if (resource_metadata_->GetIdByResourceId(
490 entry.resource_id(), &local_id) == FILE_ERROR_OK)
491 resource_metadata_->GetFilePath(local_id, &file_path);
493 if (!file_path.empty()) {
494 FileChange::ChangeType type = entry.deleted()
495 ? FileChange::CHANGE_TYPE_DELETE
496 : FileChange::CHANGE_TYPE_ADD_OR_UPDATE;
497 changed_files_->Update(file_path, entry, type);
501 } // namespace internal
502 } // namespace drive