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"
22 class ChangeListToEntryMapUMAStats
{
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_
);
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
)
62 return base::Time::FromInternalValue(remote_entry
.modification_date()) >
63 base::Time::FromInternalValue(local_entry
.modification_date());
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(
86 &entries_
[entries_index
],
87 &parent_resource_ids_
[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(
105 &entries_
[entries_index
],
106 &parent_resource_ids_
[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);
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
);
183 // Update changestamp.
184 error
= resource_metadata_
->SetLargestChangestamp(largest_changestamp
);
185 if (error
!= FILE_ERROR_OK
) {
186 DLOG(ERROR
) << "SetLargestChangeStamp failed: " << FileErrorToString(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(
199 scoped_ptr
<google_apis::AboutResource
> about_resource
) {
200 DCHECK(about_resource
);
202 // Create the entry for "My Drive" directory with the latest changestamp.
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
);
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
);
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
);
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.
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
] = "";
280 LOG(ERROR
) << "Failed to get local ID: " << parent_resource_id
281 << ", error = " << FileErrorToString(error
);
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
);
294 it_parent
= entry_map_
.find(parent_entry
.resource_id());
295 parent_local_id
= parent_entry
.parent_local_id();
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();
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
);
326 error
= resource_metadata_
->RemoveEntry(local_id
);
328 case FILE_ERROR_NOT_FOUND
:
329 error
= FILE_ERROR_OK
;
334 if (error
!= FILE_ERROR_OK
) {
335 LOG(ERROR
) << "Failed to delete: " << FileErrorToString(error
)
336 << ", resource_id = " << deleted_resource_ids
[i
];
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
,
353 if (error
!= FILE_ERROR_OK
)
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
);
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
);
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();
384 case FILE_ERROR_NOT_FOUND
: { // Adding a new entry.
385 std::string local_id
;
386 error
= resource_metadata_
->AddEntry(new_entry
, &local_id
);
392 if (error
!= FILE_ERROR_OK
)
395 UpdateChangedDirs(entry
);
396 return FILE_ERROR_OK
;
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
)
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();
431 entry
->set_parent_local_id(directory_fetch_info
.local_id());
433 std::string local_id
;
434 error
= resource_metadata
->GetIdByResourceId(entry
->resource_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
)
449 ResourceEntry result_entry
;
450 error
= resource_metadata
->GetResourceEntryById(local_id
, &result_entry
);
451 if (error
!= FILE_ERROR_OK
)
453 out_refreshed_entries
->push_back(result_entry
);
455 return FILE_ERROR_OK
;
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
;
468 FileError error
= resource_metadata
->GetIdByResourceId(
469 parent_resource_id
, &parent_local_id
);
470 if (error
!= FILE_ERROR_OK
)
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