change_list_processor.cc revision 2385ea399aae016c0806a4f9ef3c9cfe3d2a39df
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.
4
5#include "chrome/browser/chromeos/drive/change_list_processor.h"
6
7#include "base/metrics/histogram.h"
8#include "chrome/browser/chromeos/drive/drive.pb.h"
9#include "chrome/browser/chromeos/drive/file_system_util.h"
10#include "chrome/browser/chromeos/drive/resource_entry_conversion.h"
11#include "chrome/browser/chromeos/drive/resource_metadata.h"
12#include "chrome/browser/google_apis/drive_api_parser.h"
13#include "chrome/browser/google_apis/gdata_wapi_parser.h"
14
15namespace drive {
16namespace internal {
17
18ChangeList::ChangeList(const google_apis::ResourceList& resource_list)
19    : largest_changestamp_(resource_list.largest_changestamp()) {
20  resource_list.GetNextFeedURL(&next_url_);
21
22  entries_.resize(resource_list.entries().size());
23  size_t entries_index = 0;
24  for (size_t i = 0; i < resource_list.entries().size(); ++i) {
25    if (ConvertToResourceEntry(*resource_list.entries()[i],
26                               &entries_[entries_index]))
27      ++entries_index;
28  }
29  entries_.resize(entries_index);
30}
31
32ChangeList::~ChangeList() {}
33
34class ChangeListProcessor::ChangeListToEntryMapUMAStats {
35 public:
36  ChangeListToEntryMapUMAStats()
37    : num_regular_files_(0),
38      num_hosted_documents_(0),
39      num_shared_with_me_entries_(0) {
40  }
41
42  // Increments number of files.
43  void IncrementNumFiles(bool is_hosted_document) {
44    is_hosted_document ? num_hosted_documents_++ : num_regular_files_++;
45  }
46
47  // Increments number of shared-with-me entries.
48  void IncrementNumSharedWithMeEntries() {
49    num_shared_with_me_entries_++;
50  }
51
52  // Updates UMA histograms with file counts.
53  void UpdateFileCountUmaHistograms() {
54    const int num_total_files = num_hosted_documents_ + num_regular_files_;
55    UMA_HISTOGRAM_COUNTS("Drive.NumberOfRegularFiles", num_regular_files_);
56    UMA_HISTOGRAM_COUNTS("Drive.NumberOfHostedDocuments",
57                         num_hosted_documents_);
58    UMA_HISTOGRAM_COUNTS("Drive.NumberOfTotalFiles", num_total_files);
59    UMA_HISTOGRAM_COUNTS("Drive.NumberOfSharedWithMeEntries",
60                         num_shared_with_me_entries_);
61  }
62
63 private:
64  int num_regular_files_;
65  int num_hosted_documents_;
66  int num_shared_with_me_entries_;
67};
68
69ChangeListProcessor::ChangeListProcessor(ResourceMetadata* resource_metadata)
70  : resource_metadata_(resource_metadata) {
71}
72
73ChangeListProcessor::~ChangeListProcessor() {
74}
75
76void ChangeListProcessor::Apply(
77    scoped_ptr<google_apis::AboutResource> about_resource,
78    ScopedVector<ChangeList> change_lists,
79    bool is_delta_update) {
80  DCHECK(is_delta_update || about_resource.get());
81
82  int64 largest_changestamp = 0;
83  if (is_delta_update) {
84    if (!change_lists.empty()) {
85      // The changestamp appears in the first page of the change list.
86      // The changestamp does not appear in the full resource list.
87      largest_changestamp = change_lists[0]->largest_changestamp();
88      DCHECK_GE(change_lists[0]->largest_changestamp(), 0);
89    }
90  } else if (about_resource.get()) {
91    largest_changestamp = about_resource->largest_change_id();
92
93    DVLOG(1) << "Root folder ID is " << about_resource->root_folder_id();
94    DCHECK(!about_resource->root_folder_id().empty());
95  } else {
96    // A full update without AboutResouce will have no effective changestamp.
97    NOTREACHED();
98  }
99
100  ChangeListToEntryMapUMAStats uma_stats;
101  ConvertToMap(change_lists.Pass(), &entry_map_, &uma_stats);
102
103  // Add the largest changestamp for directories.
104  for (ResourceEntryMap::iterator it = entry_map_.begin();
105       it != entry_map_.end(); ++it) {
106    if (it->second.file_info().is_directory()) {
107      it->second.mutable_directory_specific_info()->set_changestamp(
108          largest_changestamp);
109    }
110  }
111
112  ApplyEntryMap(is_delta_update, about_resource.Pass());
113
114  // Update the root entry and finish.
115  UpdateRootEntry(largest_changestamp);
116
117  // Update changestamp.
118  FileError error = resource_metadata_->SetLargestChangestamp(
119      largest_changestamp);
120  DLOG_IF(ERROR, error != FILE_ERROR_OK) << "SetLargestChangeStamp failed: "
121                                         << FileErrorToString(error);
122
123  // Shouldn't record histograms when processing delta update.
124  if (!is_delta_update)
125    uma_stats.UpdateFileCountUmaHistograms();
126}
127
128void ChangeListProcessor::ApplyEntryMap(
129    bool is_delta_update,
130    scoped_ptr<google_apis::AboutResource> about_resource) {
131  if (!is_delta_update) {  // Full update.
132    DCHECK(about_resource);
133
134    FileError error = resource_metadata_->Reset();
135
136    LOG_IF(ERROR, error != FILE_ERROR_OK) << "Failed to reset: "
137                                          << FileErrorToString(error);
138
139    changed_dirs_.insert(util::GetDriveGrandRootPath());
140    changed_dirs_.insert(util::GetDriveMyDriveRootPath());
141
142    // Create the MyDrive root directory.
143    ApplyEntry(util::CreateMyDriveRootEntry(about_resource->root_folder_id()));
144  }
145
146  // Apply all entries to the metadata.
147  while (!entry_map_.empty()) {
148    // Start from entry_map_.begin() and traverse ancestors.
149    std::vector<ResourceEntryMap::iterator> entries;
150    for (ResourceEntryMap::iterator it = entry_map_.begin();
151         it != entry_map_.end();
152         it = entry_map_.find(it->second.parent_resource_id())) {
153      DCHECK_EQ(it->first, it->second.resource_id());
154      entries.push_back(it);
155    }
156
157    // Apply the parent first.
158    std::reverse(entries.begin(), entries.end());
159    for (size_t i = 0; i < entries.size(); ++i) {
160      ResourceEntryMap::iterator it = entries[i];
161      ApplyEntry(it->second);
162      entry_map_.erase(it);
163    }
164  }
165}
166
167void ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) {
168  // Lookup the entry.
169  ResourceEntry existing_entry;
170  FileError error = resource_metadata_->GetResourceEntryById(
171      entry.resource_id(), &existing_entry);
172
173  if (error == FILE_ERROR_OK) {
174    if (entry.deleted()) {
175      // Deleted file/directory.
176      RemoveEntry(entry);
177    } else {
178      // Entry exists and needs to be refreshed.
179      RefreshEntry(entry);
180    }
181  } else if (error == FILE_ERROR_NOT_FOUND && !entry.deleted()) {
182    // Adding a new entry.
183    AddEntry(entry);
184  }
185}
186
187void ChangeListProcessor::AddEntry(const ResourceEntry& entry) {
188  FileError error = resource_metadata_->AddEntry(entry);
189
190  if (error == FILE_ERROR_OK) {
191    base::FilePath file_path =
192        resource_metadata_->GetFilePath(entry.resource_id());
193    // Notify if a directory has been created.
194    if (entry.file_info().is_directory())
195      changed_dirs_.insert(file_path);
196
197    // Notify parent.
198    changed_dirs_.insert(file_path.DirName());
199  }
200}
201
202void ChangeListProcessor::RemoveEntry(const ResourceEntry& entry) {
203  std::set<base::FilePath> child_directories;
204  if (entry.file_info().is_directory()) {
205    resource_metadata_->GetChildDirectories(entry.resource_id(),
206                                            &child_directories);
207  }
208
209  base::FilePath file_path =
210      resource_metadata_->GetFilePath(entry.resource_id());
211
212  FileError error = resource_metadata_->RemoveEntry(entry.resource_id());
213
214  if (error == FILE_ERROR_OK) {
215    // Notify parent.
216    changed_dirs_.insert(file_path.DirName());
217
218    // Notify children, if any.
219    changed_dirs_.insert(child_directories.begin(), child_directories.end());
220
221    // If entry is a directory, notify self.
222    if (entry.file_info().is_directory())
223      changed_dirs_.insert(file_path);
224  }
225}
226
227void ChangeListProcessor::RefreshEntry(const ResourceEntry& entry) {
228  base::FilePath old_file_path =
229      resource_metadata_->GetFilePath(entry.resource_id());
230
231  FileError error = resource_metadata_->RefreshEntry(entry);
232
233  if (error == FILE_ERROR_OK) {
234    base::FilePath new_file_path =
235        resource_metadata_->GetFilePath(entry.resource_id());
236
237    // Notify old parent.
238    changed_dirs_.insert(old_file_path.DirName());
239
240    // Notify new parent.
241    changed_dirs_.insert(new_file_path.DirName());
242
243    // Notify self if entry is a directory.
244    if (entry.file_info().is_directory()) {
245      // Notify new self.
246      changed_dirs_.insert(new_file_path);
247      // Notify old self.
248      changed_dirs_.insert(old_file_path);
249    }
250  }
251}
252
253// static
254void ChangeListProcessor::ConvertToMap(
255    ScopedVector<ChangeList> change_lists,
256    ResourceEntryMap* entry_map,
257    ChangeListToEntryMapUMAStats* uma_stats) {
258  for (size_t i = 0; i < change_lists.size(); ++i) {
259    ChangeList* change_list = change_lists[i];
260
261    std::vector<ResourceEntry>* entries = change_list->mutable_entries();
262    for (size_t i = 0; i < entries->size(); ++i) {
263      ResourceEntry* entry = &(*entries)[i];
264      // Some document entries don't map into files (i.e. sites).
265      if (entry->resource_id().empty())
266        continue;
267
268      // Count the number of files.
269      if (uma_stats) {
270        if (!entry->file_info().is_directory()) {
271          uma_stats->IncrementNumFiles(
272              entry->file_specific_info().is_hosted_document());
273        }
274        if (entry->shared_with_me())
275          uma_stats->IncrementNumSharedWithMeEntries();
276      }
277
278      (*entry_map)[entry->resource_id()].Swap(entry);
279      LOG_IF(WARNING, !entry->resource_id().empty())
280          << "Found duplicated file: " << entry->base_name();
281    }
282  }
283}
284
285void ChangeListProcessor::UpdateRootEntry(int64 largest_changestamp) {
286  ResourceEntry root;
287  FileError error = resource_metadata_->GetResourceEntryByPath(
288      util::GetDriveMyDriveRootPath(), &root);
289
290  if (error != FILE_ERROR_OK) {
291    // TODO(satorux): Need to trigger recovery if root is corrupt.
292    LOG(WARNING) << "Failed to get the entry for root directory";
293    return;
294  }
295
296  // The changestamp should always be updated.
297  root.mutable_directory_specific_info()->set_changestamp(largest_changestamp);
298
299  error = resource_metadata_->RefreshEntry(root);
300
301  LOG_IF(WARNING, error != FILE_ERROR_OK) << "Failed to refresh root directory";
302}
303
304}  // namespace internal
305}  // namespace drive
306