Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / chromeos / drive / change_list_processor.cc
blobfb4f46e3c4a8322ec6640c705c2763e31235a3e5
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 "chrome/browser/chromeos/drive/change_list_processor.h"
7 #include "base/metrics/histogram.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "chrome/browser/chromeos/drive/drive.pb.h"
10 #include "chrome/browser/chromeos/drive/file_change.h"
11 #include "chrome/browser/chromeos/drive/file_system_util.h"
12 #include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
13 #include "chrome/browser/chromeos/drive/resource_metadata.h"
14 #include "chrome/browser/drive/drive_api_util.h"
15 #include "google_apis/drive/drive_api_parser.h"
17 namespace drive {
18 namespace internal {
20 namespace {
22 class ChangeListToEntryMapUMAStats {
23 public:
24 ChangeListToEntryMapUMAStats()
25 : num_regular_files_(0),
26 num_hosted_documents_(0),
27 num_shared_with_me_entries_(0) {
30 // Increments number of files.
31 void IncrementNumFiles(bool is_hosted_document) {
32 is_hosted_document ? num_hosted_documents_++ : num_regular_files_++;
35 // Increments number of shared-with-me entries.
36 void IncrementNumSharedWithMeEntries() {
37 num_shared_with_me_entries_++;
40 // Updates UMA histograms with file counts.
41 void UpdateFileCountUmaHistograms() {
42 const int num_total_files = num_hosted_documents_ + num_regular_files_;
43 UMA_HISTOGRAM_COUNTS("Drive.NumberOfRegularFiles", num_regular_files_);
44 UMA_HISTOGRAM_COUNTS("Drive.NumberOfHostedDocuments",
45 num_hosted_documents_);
46 UMA_HISTOGRAM_COUNTS("Drive.NumberOfTotalFiles", num_total_files);
47 UMA_HISTOGRAM_COUNTS("Drive.NumberOfSharedWithMeEntries",
48 num_shared_with_me_entries_);
51 private:
52 int num_regular_files_;
53 int num_hosted_documents_;
54 int num_shared_with_me_entries_;
57 // Returns true if it's OK to overwrite the local entry with the remote one.
58 bool ShouldApplyChange(const ResourceEntry& local_entry,
59 const ResourceEntry& remote_entry) {
60 if (local_entry.metadata_edit_state() == ResourceEntry::CLEAN)
61 return true;
62 return base::Time::FromInternalValue(remote_entry.modification_date()) >
63 base::Time::FromInternalValue(local_entry.modification_date());
66 } // namespace
68 std::string DirectoryFetchInfo::ToString() const {
69 return ("local_id: " + local_id_ +
70 ", resource_id: " + resource_id_ +
71 ", changestamp: " + base::Int64ToString(changestamp_));
74 ChangeList::ChangeList() {}
76 ChangeList::ChangeList(const google_apis::ChangeList& change_list)
77 : next_url_(change_list.next_link()),
78 largest_changestamp_(change_list.largest_change_id()) {
79 const ScopedVector<google_apis::ChangeResource>& items = change_list.items();
80 entries_.resize(items.size());
81 parent_resource_ids_.resize(items.size());
82 size_t entries_index = 0;
83 for (size_t i = 0; i < items.size(); ++i) {
84 if (ConvertChangeResourceToResourceEntry(
85 *items[i],
86 &entries_[entries_index],
87 &parent_resource_ids_[entries_index])) {
88 ++entries_index;
91 entries_.resize(entries_index);
92 parent_resource_ids_.resize(entries_index);
95 ChangeList::ChangeList(const google_apis::FileList& file_list)
96 : next_url_(file_list.next_link()),
97 largest_changestamp_(0) {
98 const ScopedVector<google_apis::FileResource>& items = file_list.items();
99 entries_.resize(items.size());
100 parent_resource_ids_.resize(items.size());
101 size_t entries_index = 0;
102 for (size_t i = 0; i < items.size(); ++i) {
103 if (ConvertFileResourceToResourceEntry(
104 *items[i],
105 &entries_[entries_index],
106 &parent_resource_ids_[entries_index])) {
107 ++entries_index;
110 entries_.resize(entries_index);
111 parent_resource_ids_.resize(entries_index);
114 ChangeList::~ChangeList() {}
116 ChangeListProcessor::ChangeListProcessor(ResourceMetadata* resource_metadata)
117 : resource_metadata_(resource_metadata), changed_files_(new FileChange) {
120 ChangeListProcessor::~ChangeListProcessor() {
123 FileError ChangeListProcessor::Apply(
124 scoped_ptr<google_apis::AboutResource> about_resource,
125 ScopedVector<ChangeList> change_lists,
126 bool is_delta_update) {
127 DCHECK(about_resource);
129 int64 largest_changestamp = 0;
130 if (is_delta_update) {
131 if (!change_lists.empty()) {
132 // The changestamp appears in the first page of the change list.
133 // The changestamp does not appear in the full resource list.
134 largest_changestamp = change_lists[0]->largest_changestamp();
135 DCHECK_GE(change_lists[0]->largest_changestamp(), 0);
137 } else {
138 largest_changestamp = about_resource->largest_change_id();
140 DVLOG(1) << "Root folder ID is " << about_resource->root_folder_id();
141 DCHECK(!about_resource->root_folder_id().empty());
144 // Convert ChangeList to map.
145 ChangeListToEntryMapUMAStats uma_stats;
146 for (size_t i = 0; i < change_lists.size(); ++i) {
147 ChangeList* change_list = change_lists[i];
149 std::vector<ResourceEntry>* entries = change_list->mutable_entries();
150 for (size_t i = 0; i < entries->size(); ++i) {
151 ResourceEntry* entry = &(*entries)[i];
153 // Count the number of files.
154 if (!entry->file_info().is_directory()) {
155 uma_stats.IncrementNumFiles(
156 entry->file_specific_info().is_hosted_document());
157 if (entry->shared_with_me())
158 uma_stats.IncrementNumSharedWithMeEntries();
160 parent_resource_id_map_[entry->resource_id()] =
161 change_list->parent_resource_ids()[i];
162 entry_map_[entry->resource_id()].Swap(entry);
163 LOG_IF(WARNING, !entry->resource_id().empty())
164 << "Found duplicated file: " << entry->base_name();
168 // Add the largest changestamp for directories.
169 for (ResourceEntryMap::iterator it = entry_map_.begin();
170 it != entry_map_.end(); ++it) {
171 if (it->second.file_info().is_directory()) {
172 it->second.mutable_directory_specific_info()->set_changestamp(
173 largest_changestamp);
177 FileError error = ApplyEntryMap(largest_changestamp, about_resource.Pass());
178 if (error != FILE_ERROR_OK) {
179 DLOG(ERROR) << "ApplyEntryMap failed: " << FileErrorToString(error);
180 return error;
183 // Update changestamp.
184 error = resource_metadata_->SetLargestChangestamp(largest_changestamp);
185 if (error != FILE_ERROR_OK) {
186 DLOG(ERROR) << "SetLargestChangeStamp failed: " << FileErrorToString(error);
187 return error;
190 // Shouldn't record histograms when processing delta update.
191 if (!is_delta_update)
192 uma_stats.UpdateFileCountUmaHistograms();
194 return FILE_ERROR_OK;
197 FileError ChangeListProcessor::ApplyEntryMap(
198 int64 changestamp,
199 scoped_ptr<google_apis::AboutResource> about_resource) {
200 DCHECK(about_resource);
202 // Create the entry for "My Drive" directory with the latest changestamp.
203 ResourceEntry root;
204 FileError error = resource_metadata_->GetResourceEntryByPath(
205 util::GetDriveMyDriveRootPath(), &root);
206 if (error != FILE_ERROR_OK) {
207 LOG(ERROR) << "Failed to get root entry: " << FileErrorToString(error);
208 return error;
211 root.mutable_directory_specific_info()->set_changestamp(changestamp);
212 root.set_resource_id(about_resource->root_folder_id());
213 error = resource_metadata_->RefreshEntry(root);
214 if (error != FILE_ERROR_OK) {
215 LOG(ERROR) << "Failed to update root entry: " << FileErrorToString(error);
216 return error;
219 // Gather the set of changes in the old path.
220 // Note that we want to notify the change in both old and new paths (suppose
221 // /a/b/c is moved to /x/y/c. We want to notify both "/a/b" and "/x/y".)
222 // The old paths must be calculated before we apply any actual changes.
223 // The new paths are calculated after each change is applied. It correctly
224 // sets the new path because we apply changes in such an order (see below).
225 for (ResourceEntryMap::iterator it = entry_map_.begin();
226 it != entry_map_.end(); ++it) {
227 UpdateChangedDirs(it->second);
230 // Apply all entries except deleted ones to the metadata.
231 std::vector<std::string> deleted_resource_ids;
232 while (!entry_map_.empty()) {
233 ResourceEntryMap::iterator it = entry_map_.begin();
235 // Process deleted entries later to avoid deleting moved entries under it.
236 if (it->second.deleted()) {
237 deleted_resource_ids.push_back(it->first);
238 entry_map_.erase(it);
239 continue;
242 // Start from entry_map_.begin() and traverse ancestors using the
243 // parent-child relationships in the result (after this apply) tree.
244 // Then apply the topmost change first.
246 // By doing this, assuming the result tree does not contain any cycles, we
247 // can guarantee that no cycle is made during this apply (i.e. no entry gets
248 // moved under any of its descendants) because the following conditions are
249 // always satisfied in any move:
250 // - The new parent entry is not a descendant of the moved entry.
251 // - The new parent and its ancestors will no longer move during this apply.
252 std::vector<ResourceEntryMap::iterator> entries;
253 for (ResourceEntryMap::iterator it = entry_map_.begin();
254 it != entry_map_.end();) {
255 entries.push_back(it);
257 DCHECK(parent_resource_id_map_.count(it->first)) << it->first;
258 const std::string& parent_resource_id =
259 parent_resource_id_map_[it->first];
261 if (parent_resource_id.empty()) // This entry has no parent.
262 break;
264 ResourceEntryMap::iterator it_parent =
265 entry_map_.find(parent_resource_id);
266 if (it_parent == entry_map_.end()) {
267 // Current entry's parent is already updated or not going to be updated,
268 // get the parent from the local tree.
269 std::string parent_local_id;
270 FileError error = resource_metadata_->GetIdByResourceId(
271 parent_resource_id, &parent_local_id);
272 if (error != FILE_ERROR_OK) {
273 // See crbug.com/326043. In some complicated situations, parent folder
274 // for shared entries may be accessible (and hence its resource id is
275 // included), but not in the change/file list.
276 // In such a case, clear the parent and move it to drive/other.
277 if (error == FILE_ERROR_NOT_FOUND) {
278 parent_resource_id_map_[it->first] = "";
279 } else {
280 LOG(ERROR) << "Failed to get local ID: " << parent_resource_id
281 << ", error = " << FileErrorToString(error);
283 break;
285 ResourceEntry parent_entry;
286 while (it_parent == entry_map_.end() && !parent_local_id.empty()) {
287 error = resource_metadata_->GetResourceEntryById(
288 parent_local_id, &parent_entry);
289 if (error != FILE_ERROR_OK) {
290 LOG(ERROR) << "Failed to get local entry: "
291 << FileErrorToString(error);
292 break;
294 it_parent = entry_map_.find(parent_entry.resource_id());
295 parent_local_id = parent_entry.parent_local_id();
298 it = it_parent;
301 // Apply the parent first.
302 std::reverse(entries.begin(), entries.end());
303 for (size_t i = 0; i < entries.size(); ++i) {
304 // Skip root entry in the change list. We don't expect servers to send
305 // root entry, but we should better be defensive (see crbug.com/297259).
306 ResourceEntryMap::iterator it = entries[i];
307 if (it->first != root.resource_id()) {
308 FileError error = ApplyEntry(it->second);
309 if (error != FILE_ERROR_OK) {
310 LOG(ERROR) << "ApplyEntry failed: " << FileErrorToString(error)
311 << ", title = " << it->second.title();
312 return error;
315 entry_map_.erase(it);
319 // Apply deleted entries.
320 for (size_t i = 0; i < deleted_resource_ids.size(); ++i) {
321 std::string local_id;
322 FileError error = resource_metadata_->GetIdByResourceId(
323 deleted_resource_ids[i], &local_id);
324 switch (error) {
325 case FILE_ERROR_OK:
326 error = resource_metadata_->RemoveEntry(local_id);
327 break;
328 case FILE_ERROR_NOT_FOUND:
329 error = FILE_ERROR_OK;
330 break;
331 default:
332 break;
334 if (error != FILE_ERROR_OK) {
335 LOG(ERROR) << "Failed to delete: " << FileErrorToString(error)
336 << ", resource_id = " << deleted_resource_ids[i];
337 return error;
341 return FILE_ERROR_OK;
344 FileError ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) {
345 DCHECK(!entry.deleted());
346 DCHECK(parent_resource_id_map_.count(entry.resource_id()));
347 const std::string& parent_resource_id =
348 parent_resource_id_map_[entry.resource_id()];
350 ResourceEntry new_entry(entry);
351 FileError error = SetParentLocalIdOfEntry(resource_metadata_, &new_entry,
352 parent_resource_id);
353 if (error != FILE_ERROR_OK)
354 return error;
356 // Lookup the entry.
357 std::string local_id;
358 error = resource_metadata_->GetIdByResourceId(entry.resource_id(), &local_id);
360 ResourceEntry existing_entry;
361 if (error == FILE_ERROR_OK)
362 error = resource_metadata_->GetResourceEntryById(local_id, &existing_entry);
364 switch (error) {
365 case FILE_ERROR_OK:
366 if (ShouldApplyChange(existing_entry, new_entry)) {
367 // Entry exists and needs to be refreshed.
368 new_entry.set_local_id(local_id);
369 // Keep the to-be-synced properties of the existing resource entry.
370 new_entry.mutable_new_properties()->CopyFrom(
371 existing_entry.new_properties());
372 error = resource_metadata_->RefreshEntry(new_entry);
373 } else {
374 if (entry.file_info().is_directory()) {
375 // No need to refresh, but update the changestamp.
376 new_entry = existing_entry;
377 new_entry.mutable_directory_specific_info()->set_changestamp(
378 new_entry.directory_specific_info().changestamp());
379 error = resource_metadata_->RefreshEntry(new_entry);
381 DVLOG(1) << "Change was discarded for: " << entry.resource_id();
383 break;
384 case FILE_ERROR_NOT_FOUND: { // Adding a new entry.
385 std::string local_id;
386 error = resource_metadata_->AddEntry(new_entry, &local_id);
387 break;
389 default:
390 return error;
392 if (error != FILE_ERROR_OK)
393 return error;
395 UpdateChangedDirs(entry);
396 return FILE_ERROR_OK;
399 // static
400 FileError ChangeListProcessor::RefreshDirectory(
401 ResourceMetadata* resource_metadata,
402 const DirectoryFetchInfo& directory_fetch_info,
403 scoped_ptr<ChangeList> change_list,
404 std::vector<ResourceEntry>* out_refreshed_entries) {
405 DCHECK(!directory_fetch_info.empty());
407 ResourceEntry directory;
408 FileError error = resource_metadata->GetResourceEntryById(
409 directory_fetch_info.local_id(), &directory);
410 if (error != FILE_ERROR_OK)
411 return error;
413 if (!directory.file_info().is_directory())
414 return FILE_ERROR_NOT_A_DIRECTORY;
416 std::vector<ResourceEntry>* entries = change_list->mutable_entries();
417 for (size_t i = 0; i < entries->size(); ++i) {
418 ResourceEntry* entry = &(*entries)[i];
419 const std::string& parent_resource_id =
420 change_list->parent_resource_ids()[i];
422 // Skip if the parent resource ID does not match. This is needed to
423 // handle entries with multiple parents. For such entries, the first
424 // parent is picked and other parents are ignored, hence some entries may
425 // have a parent resource ID which does not match the target directory's.
426 if (parent_resource_id != directory_fetch_info.resource_id()) {
427 DVLOG(1) << "Wrong-parent entry rejected: " << entry->resource_id();
428 continue;
431 entry->set_parent_local_id(directory_fetch_info.local_id());
433 std::string local_id;
434 error = resource_metadata->GetIdByResourceId(entry->resource_id(),
435 &local_id);
436 if (error == FILE_ERROR_OK) {
437 entry->set_local_id(local_id);
438 error = resource_metadata->RefreshEntry(*entry);
441 if (error == FILE_ERROR_NOT_FOUND) { // If refreshing fails, try adding.
442 entry->clear_local_id();
443 error = resource_metadata->AddEntry(*entry, &local_id);
446 if (error != FILE_ERROR_OK)
447 return error;
449 ResourceEntry result_entry;
450 error = resource_metadata->GetResourceEntryById(local_id, &result_entry);
451 if (error != FILE_ERROR_OK)
452 return error;
453 out_refreshed_entries->push_back(result_entry);
455 return FILE_ERROR_OK;
458 // static
459 FileError ChangeListProcessor::SetParentLocalIdOfEntry(
460 ResourceMetadata* resource_metadata,
461 ResourceEntry* entry,
462 const std::string& parent_resource_id) {
463 std::string parent_local_id;
464 if (parent_resource_id.empty()) {
465 // Entries without parents should go under "other" directory.
466 parent_local_id = util::kDriveOtherDirLocalId;
467 } else {
468 FileError error = resource_metadata->GetIdByResourceId(
469 parent_resource_id, &parent_local_id);
470 if (error != FILE_ERROR_OK)
471 return error;
473 entry->set_parent_local_id(parent_local_id);
474 return FILE_ERROR_OK;
477 void ChangeListProcessor::UpdateChangedDirs(const ResourceEntry& entry) {
478 DCHECK(!entry.resource_id().empty());
480 std::string local_id;
481 base::FilePath file_path;
482 if (resource_metadata_->GetIdByResourceId(
483 entry.resource_id(), &local_id) == FILE_ERROR_OK)
484 resource_metadata_->GetFilePath(local_id, &file_path);
486 if (!file_path.empty()) {
487 FileChange::ChangeType type =
488 entry.deleted() ? FileChange::DELETE : FileChange::ADD_OR_UPDATE;
489 changed_files_->Update(file_path, entry, type);
493 } // namespace internal
494 } // namespace drive