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"
21 class ChangeListToEntryMapUMAStats
{
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_
);
51 int num_regular_files_
;
52 int num_hosted_documents_
;
53 int num_shared_with_me_entries_
;
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
]))
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);
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
);
152 // Update changestamp.
153 error
= resource_metadata_
->SetLargestChangestamp(largest_changestamp
);
154 if (error
!= FILE_ERROR_OK
) {
155 DLOG(ERROR
) << "SetLargestChangeStamp failed: " << FileErrorToString(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(
168 scoped_ptr
<google_apis::AboutResource
> about_resource
) {
169 DCHECK(about_resource
);
171 // Create the entry for "My Drive" directory with the latest changestamp.
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
);
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
);
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
);
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.
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
] = "";
249 LOG(ERROR
) << "Failed to get local ID: " << parent_resource_id
250 << ", error = " << FileErrorToString(error
);
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
);
263 it_parent
= entry_map_
.find(parent_entry
.resource_id());
264 parent_local_id
= parent_entry
.parent_local_id();
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
,
312 if (error
!= FILE_ERROR_OK
)
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
);
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
);
328 case FILE_ERROR_NOT_FOUND
: { // Adding a new entry.
329 std::string local_id
;
330 error
= resource_metadata_
->AddEntry(new_entry
, &local_id
);
336 if (error
!= FILE_ERROR_OK
)
339 UpdateChangedDirs(entry
);
340 return FILE_ERROR_OK
;
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
)
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();
377 entry
->set_parent_local_id(directory_fetch_info
.local_id());
379 std::string local_id
;
380 error
= resource_metadata
->GetIdByResourceId(entry
->resource_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
)
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
)
404 *out_file_path
= resource_metadata
->GetFilePath(
405 directory_fetch_info
.local_id());
406 return FILE_ERROR_OK
;
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
;
419 FileError error
= resource_metadata
->GetIdByResourceId(
420 parent_resource_id
, &parent_local_id
);
421 if (error
!= FILE_ERROR_OK
)
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()) {
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
,
450 changed_dirs_
.insert(sub_directories
.begin(), sub_directories
.end());
456 } // namespace internal