Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / chromeos / drive / change_list_processor.cc
bloba56e5a647e8ddd2ece42c7ecd44933221afcf3c6
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_system_util.h"
11 #include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
12 #include "chrome/browser/chromeos/drive/resource_metadata.h"
13 #include "google_apis/drive/drive_api_parser.h"
14 #include "google_apis/drive/gdata_wapi_parser.h"
16 namespace drive {
17 namespace internal {
19 namespace {
21 class ChangeListToEntryMapUMAStats {
22 public:
23 ChangeListToEntryMapUMAStats()
24 : num_regular_files_(0),
25 num_hosted_documents_(0),
26 num_shared_with_me_entries_(0) {
29 // Increments number of files.
30 void IncrementNumFiles(bool is_hosted_document) {
31 is_hosted_document ? num_hosted_documents_++ : num_regular_files_++;
34 // Increments number of shared-with-me entries.
35 void IncrementNumSharedWithMeEntries() {
36 num_shared_with_me_entries_++;
39 // Updates UMA histograms with file counts.
40 void UpdateFileCountUmaHistograms() {
41 const int num_total_files = num_hosted_documents_ + num_regular_files_;
42 UMA_HISTOGRAM_COUNTS("Drive.NumberOfRegularFiles", num_regular_files_);
43 UMA_HISTOGRAM_COUNTS("Drive.NumberOfHostedDocuments",
44 num_hosted_documents_);
45 UMA_HISTOGRAM_COUNTS("Drive.NumberOfTotalFiles", num_total_files);
46 UMA_HISTOGRAM_COUNTS("Drive.NumberOfSharedWithMeEntries",
47 num_shared_with_me_entries_);
50 private:
51 int num_regular_files_;
52 int num_hosted_documents_;
53 int num_shared_with_me_entries_;
56 } // namespace
58 std::string DirectoryFetchInfo::ToString() const {
59 return ("local_id: " + local_id_ +
60 ", resource_id: " + resource_id_ +
61 ", changestamp: " + base::Int64ToString(changestamp_));
64 ChangeList::ChangeList() {}
66 ChangeList::ChangeList(const google_apis::ResourceList& resource_list)
67 : largest_changestamp_(resource_list.largest_changestamp()) {
68 resource_list.GetNextFeedURL(&next_url_);
70 entries_.resize(resource_list.entries().size());
71 parent_resource_ids_.resize(resource_list.entries().size());
72 size_t entries_index = 0;
73 for (size_t i = 0; i < resource_list.entries().size(); ++i) {
74 if (ConvertToResourceEntry(*resource_list.entries()[i],
75 &entries_[entries_index],
76 &parent_resource_ids_[entries_index]))
77 ++entries_index;
79 entries_.resize(entries_index);
80 parent_resource_ids_.resize(entries_index);
83 ChangeList::~ChangeList() {}
85 ChangeListProcessor::ChangeListProcessor(ResourceMetadata* resource_metadata)
86 : resource_metadata_(resource_metadata) {
89 ChangeListProcessor::~ChangeListProcessor() {
92 FileError ChangeListProcessor::Apply(
93 scoped_ptr<google_apis::AboutResource> about_resource,
94 ScopedVector<ChangeList> change_lists,
95 bool is_delta_update) {
96 DCHECK(about_resource);
98 int64 largest_changestamp = 0;
99 if (is_delta_update) {
100 if (!change_lists.empty()) {
101 // The changestamp appears in the first page of the change list.
102 // The changestamp does not appear in the full resource list.
103 largest_changestamp = change_lists[0]->largest_changestamp();
104 DCHECK_GE(change_lists[0]->largest_changestamp(), 0);
106 } else {
107 largest_changestamp = about_resource->largest_change_id();
109 DVLOG(1) << "Root folder ID is " << about_resource->root_folder_id();
110 DCHECK(!about_resource->root_folder_id().empty());
113 // Convert ChangeList to map.
114 ChangeListToEntryMapUMAStats uma_stats;
115 for (size_t i = 0; i < change_lists.size(); ++i) {
116 ChangeList* change_list = change_lists[i];
118 std::vector<ResourceEntry>* entries = change_list->mutable_entries();
119 for (size_t i = 0; i < entries->size(); ++i) {
120 ResourceEntry* entry = &(*entries)[i];
122 // Count the number of files.
123 if (!entry->file_info().is_directory()) {
124 uma_stats.IncrementNumFiles(
125 entry->file_specific_info().is_hosted_document());
126 if (entry->shared_with_me())
127 uma_stats.IncrementNumSharedWithMeEntries();
129 parent_resource_id_map_[entry->resource_id()] =
130 change_list->parent_resource_ids()[i];
131 entry_map_[entry->resource_id()].Swap(entry);
132 LOG_IF(WARNING, !entry->resource_id().empty())
133 << "Found duplicated file: " << entry->base_name();
137 // Add the largest changestamp for directories.
138 for (ResourceEntryMap::iterator it = entry_map_.begin();
139 it != entry_map_.end(); ++it) {
140 if (it->second.file_info().is_directory()) {
141 it->second.mutable_directory_specific_info()->set_changestamp(
142 largest_changestamp);
146 FileError error = ApplyEntryMap(largest_changestamp, about_resource.Pass());
147 if (error != FILE_ERROR_OK) {
148 DLOG(ERROR) << "ApplyEntryMap failed: " << FileErrorToString(error);
149 return error;
152 // Update changestamp.
153 error = resource_metadata_->SetLargestChangestamp(largest_changestamp);
154 if (error != FILE_ERROR_OK) {
155 DLOG(ERROR) << "SetLargestChangeStamp failed: " << FileErrorToString(error);
156 return error;
159 // Shouldn't record histograms when processing delta update.
160 if (!is_delta_update)
161 uma_stats.UpdateFileCountUmaHistograms();
163 return FILE_ERROR_OK;
166 FileError ChangeListProcessor::ApplyEntryMap(
167 int64 changestamp,
168 scoped_ptr<google_apis::AboutResource> about_resource) {
169 DCHECK(about_resource);
171 // Create the entry for "My Drive" directory with the latest changestamp.
172 ResourceEntry root;
173 FileError error = resource_metadata_->GetResourceEntryByPath(
174 util::GetDriveMyDriveRootPath(), &root);
175 if (error != FILE_ERROR_OK) {
176 LOG(ERROR) << "Failed to get root entry: " << FileErrorToString(error);
177 return error;
180 root.mutable_directory_specific_info()->set_changestamp(changestamp);
181 root.set_resource_id(about_resource->root_folder_id());
182 error = resource_metadata_->RefreshEntry(root);
183 if (error != FILE_ERROR_OK) {
184 LOG(ERROR) << "Failed to update root entry: " << FileErrorToString(error);
185 return error;
188 // Gather the set of changes in the old path.
189 // Note that we want to notify the change in both old and new paths (suppose
190 // /a/b/c is moved to /x/y/c. We want to notify both "/a/b" and "/x/y".)
191 // The old paths must be calculated before we apply any actual changes.
192 // The new paths are calculated after each change is applied. It correctly
193 // sets the new path because we apply changes in such an order (see below).
194 for (ResourceEntryMap::iterator it = entry_map_.begin();
195 it != entry_map_.end(); ++it) {
196 UpdateChangedDirs(it->second);
199 // Apply all entries except deleted ones to the metadata.
200 std::vector<std::string> deleted_resource_ids;
201 while (!entry_map_.empty()) {
202 ResourceEntryMap::iterator it = entry_map_.begin();
204 // Process deleted entries later to avoid deleting moved entries under it.
205 if (it->second.deleted()) {
206 deleted_resource_ids.push_back(it->first);
207 entry_map_.erase(it);
208 continue;
211 // Start from entry_map_.begin() and traverse ancestors using the
212 // parent-child relationships in the result (after this apply) tree.
213 // Then apply the topmost change first.
215 // By doing this, assuming the result tree does not contain any cycles, we
216 // can guarantee that no cycle is made during this apply (i.e. no entry gets
217 // moved under any of its descendants) because the following conditions are
218 // always satisfied in any move:
219 // - The new parent entry is not a descendant of the moved entry.
220 // - The new parent and its ancestors will no longer move during this apply.
221 std::vector<ResourceEntryMap::iterator> entries;
222 for (ResourceEntryMap::iterator it = entry_map_.begin();
223 it != entry_map_.end();) {
224 entries.push_back(it);
226 DCHECK(parent_resource_id_map_.count(it->first)) << it->first;
227 const std::string& parent_resource_id =
228 parent_resource_id_map_[it->first];
230 if (parent_resource_id.empty()) // This entry has no parent.
231 break;
233 ResourceEntryMap::iterator it_parent =
234 entry_map_.find(parent_resource_id);
235 if (it_parent == entry_map_.end()) {
236 // Current entry's parent is already updated or not going to be updated,
237 // get the parent from the local tree.
238 std::string parent_local_id;
239 FileError error = resource_metadata_->GetIdByResourceId(
240 parent_resource_id, &parent_local_id);
241 if (error != FILE_ERROR_OK) {
242 // See crbug.com/326043. In some complicated situations, parent folder
243 // for shared entries may be accessible (and hence its resource id is
244 // included), but not in the change/file list.
245 // In such a case, clear the parent and move it to drive/other.
246 if (error == FILE_ERROR_NOT_FOUND) {
247 parent_resource_id_map_[it->first] = "";
248 } else {
249 LOG(ERROR) << "Failed to get local ID: " << parent_resource_id
250 << ", error = " << FileErrorToString(error);
252 break;
254 ResourceEntry parent_entry;
255 while (it_parent == entry_map_.end() && !parent_local_id.empty()) {
256 error = resource_metadata_->GetResourceEntryById(
257 parent_local_id, &parent_entry);
258 if (error != FILE_ERROR_OK) {
259 LOG(ERROR) << "Failed to get local entry: "
260 << FileErrorToString(error);
261 break;
263 it_parent = entry_map_.find(parent_entry.resource_id());
264 parent_local_id = parent_entry.parent_local_id();
267 it = it_parent;
270 // Apply the parent first.
271 std::reverse(entries.begin(), entries.end());
272 for (size_t i = 0; i < entries.size(); ++i) {
273 // Skip root entry in the change list. We don't expect servers to send
274 // root entry, but we should better be defensive (see crbug.com/297259).
275 ResourceEntryMap::iterator it = entries[i];
276 if (it->first != root.resource_id()) {
277 // TODO(hashimoto): Handle ApplyEntry errors correctly.
278 FileError error = ApplyEntry(it->second);
279 DLOG_IF(WARNING, error != FILE_ERROR_OK)
280 << "ApplyEntry failed: " << FileErrorToString(error)
281 << ", title = " << it->second.title();
283 entry_map_.erase(it);
287 // Apply deleted entries.
288 for (size_t i = 0; i < deleted_resource_ids.size(); ++i) {
289 std::string local_id;
290 FileError error = resource_metadata_->GetIdByResourceId(
291 deleted_resource_ids[i], &local_id);
292 if (error == FILE_ERROR_OK)
293 error = resource_metadata_->RemoveEntry(local_id);
295 DLOG_IF(WARNING, error != FILE_ERROR_OK && error != FILE_ERROR_NOT_FOUND)
296 << "Failed to delete: " << FileErrorToString(error)
297 << ", resource_id = " << deleted_resource_ids[i];
300 return FILE_ERROR_OK;
303 FileError ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) {
304 DCHECK(!entry.deleted());
305 DCHECK(parent_resource_id_map_.count(entry.resource_id()));
306 const std::string& parent_resource_id =
307 parent_resource_id_map_[entry.resource_id()];
309 ResourceEntry new_entry(entry);
310 FileError error = SetParentLocalIdOfEntry(resource_metadata_, &new_entry,
311 parent_resource_id);
312 if (error != FILE_ERROR_OK)
313 return error;
315 // Lookup the entry.
316 std::string local_id;
317 error = resource_metadata_->GetIdByResourceId(entry.resource_id(), &local_id);
319 ResourceEntry existing_entry;
320 if (error == FILE_ERROR_OK)
321 error = resource_metadata_->GetResourceEntryById(local_id, &existing_entry);
323 switch (error) {
324 case FILE_ERROR_OK: // Entry exists and needs to be refreshed.
325 new_entry.set_local_id(local_id);
326 error = resource_metadata_->RefreshEntry(new_entry);
327 break;
328 case FILE_ERROR_NOT_FOUND: { // Adding a new entry.
329 std::string local_id;
330 error = resource_metadata_->AddEntry(new_entry, &local_id);
331 break;
333 default:
334 return error;
336 if (error != FILE_ERROR_OK)
337 return error;
339 UpdateChangedDirs(entry);
340 return FILE_ERROR_OK;
343 // static
344 FileError ChangeListProcessor::RefreshDirectory(
345 ResourceMetadata* resource_metadata,
346 const DirectoryFetchInfo& directory_fetch_info,
347 ScopedVector<ChangeList> change_lists,
348 base::FilePath* out_file_path) {
349 DCHECK(!directory_fetch_info.empty());
351 ResourceEntry directory;
352 FileError error = resource_metadata->GetResourceEntryById(
353 directory_fetch_info.local_id(), &directory);
354 if (error != FILE_ERROR_OK)
355 return error;
357 if (!directory.file_info().is_directory())
358 return FILE_ERROR_NOT_A_DIRECTORY;
360 for (size_t i = 0; i < change_lists.size(); ++i) {
361 ChangeList* change_list = change_lists[i];
362 std::vector<ResourceEntry>* entries = change_list->mutable_entries();
363 for (size_t i = 0; i < entries->size(); ++i) {
364 ResourceEntry* entry = &(*entries)[i];
365 const std::string& parent_resource_id =
366 change_list->parent_resource_ids()[i];
368 // Skip if the parent resource ID does not match. This is needed to
369 // handle entries with multiple parents. For such entries, the first
370 // parent is picked and other parents are ignored, hence some entries may
371 // have a parent resource ID which does not match the target directory's.
372 if (parent_resource_id != directory_fetch_info.resource_id()) {
373 DVLOG(1) << "Wrong-parent entry rejected: " << entry->resource_id();
374 continue;
377 entry->set_parent_local_id(directory_fetch_info.local_id());
379 std::string local_id;
380 error = resource_metadata->GetIdByResourceId(entry->resource_id(),
381 &local_id);
382 if (error == FILE_ERROR_OK) {
383 entry->set_local_id(local_id);
384 error = resource_metadata->RefreshEntry(*entry);
387 if (error == FILE_ERROR_NOT_FOUND) { // If refreshing fails, try adding.
388 std::string local_id;
389 entry->clear_local_id();
390 error = resource_metadata->AddEntry(*entry, &local_id);
393 if (error != FILE_ERROR_OK)
394 return error;
398 directory.mutable_directory_specific_info()->set_changestamp(
399 directory_fetch_info.changestamp());
400 error = resource_metadata->RefreshEntry(directory);
401 if (error != FILE_ERROR_OK)
402 return error;
404 *out_file_path = resource_metadata->GetFilePath(
405 directory_fetch_info.local_id());
406 return FILE_ERROR_OK;
409 // static
410 FileError ChangeListProcessor::SetParentLocalIdOfEntry(
411 ResourceMetadata* resource_metadata,
412 ResourceEntry* entry,
413 const std::string& parent_resource_id) {
414 std::string parent_local_id;
415 if (parent_resource_id.empty()) {
416 // Entries without parents should go under "other" directory.
417 parent_local_id = util::kDriveOtherDirLocalId;
418 } else {
419 FileError error = resource_metadata->GetIdByResourceId(
420 parent_resource_id, &parent_local_id);
421 if (error != FILE_ERROR_OK)
422 return error;
424 entry->set_parent_local_id(parent_local_id);
425 return FILE_ERROR_OK;
428 void ChangeListProcessor::UpdateChangedDirs(const ResourceEntry& entry) {
429 DCHECK(!entry.resource_id().empty());
431 std::string local_id;
432 base::FilePath file_path;
433 if (resource_metadata_->GetIdByResourceId(
434 entry.resource_id(), &local_id) == FILE_ERROR_OK)
435 file_path = resource_metadata_->GetFilePath(local_id);
437 if (!file_path.empty()) {
438 // Notify parent.
439 changed_dirs_.insert(file_path.DirName());
441 if (entry.file_info().is_directory()) {
442 // Notify self if entry is a directory.
443 changed_dirs_.insert(file_path);
445 // Notify all descendants if it is a directory deletion.
446 if (entry.deleted()) {
447 std::set<base::FilePath> sub_directories;
448 resource_metadata_->GetSubDirectoriesRecursively(local_id,
449 &sub_directories);
450 changed_dirs_.insert(sub_directories.begin(), sub_directories.end());
456 } // namespace internal
457 } // namespace drive