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 "base/synchronization/cancellation_flag.h"
10 #include "components/drive/drive.pb.h"
11 #include "components/drive/drive_api_util.h"
12 #include "components/drive/file_change.h"
13 #include "components/drive/file_system_core_util.h"
14 #include "components/drive/resource_entry_conversion.h"
15 #include "components/drive/resource_metadata.h"
16 #include "google_apis/drive/drive_api_parser.h"
23 class ChangeListToEntryMapUMAStats
{
25 ChangeListToEntryMapUMAStats()
26 : num_regular_files_(0),
27 num_hosted_documents_(0),
28 num_shared_with_me_entries_(0) {
31 // Increments number of files.
32 void IncrementNumFiles(bool is_hosted_document
) {
33 is_hosted_document
? num_hosted_documents_
++ : num_regular_files_
++;
36 // Increments number of shared-with-me entries.
37 void IncrementNumSharedWithMeEntries() {
38 num_shared_with_me_entries_
++;
41 // Updates UMA histograms with file counts.
42 void UpdateFileCountUmaHistograms() {
43 const int num_total_files
= num_hosted_documents_
+ num_regular_files_
;
44 UMA_HISTOGRAM_COUNTS("Drive.NumberOfRegularFiles", num_regular_files_
);
45 UMA_HISTOGRAM_COUNTS("Drive.NumberOfHostedDocuments",
46 num_hosted_documents_
);
47 UMA_HISTOGRAM_COUNTS("Drive.NumberOfTotalFiles", num_total_files
);
48 UMA_HISTOGRAM_COUNTS("Drive.NumberOfSharedWithMeEntries",
49 num_shared_with_me_entries_
);
53 int num_regular_files_
;
54 int num_hosted_documents_
;
55 int num_shared_with_me_entries_
;
58 // Returns true if it's OK to overwrite the local entry with the remote one.
59 bool ShouldApplyChange(const ResourceEntry
& local_entry
,
60 const ResourceEntry
& remote_entry
) {
61 if (local_entry
.metadata_edit_state() == ResourceEntry::CLEAN
)
63 return base::Time::FromInternalValue(remote_entry
.modification_date()) >
64 base::Time::FromInternalValue(local_entry
.modification_date());
69 std::string
DirectoryFetchInfo::ToString() const {
70 return ("local_id: " + local_id_
+
71 ", resource_id: " + resource_id_
+
72 ", changestamp: " + base::Int64ToString(changestamp_
));
75 ChangeList::ChangeList() {}
77 ChangeList::ChangeList(const google_apis::ChangeList
& change_list
)
78 : next_url_(change_list
.next_link()),
79 largest_changestamp_(change_list
.largest_change_id()) {
80 const ScopedVector
<google_apis::ChangeResource
>& items
= change_list
.items();
81 entries_
.resize(items
.size());
82 parent_resource_ids_
.resize(items
.size());
83 size_t entries_index
= 0;
84 for (size_t i
= 0; i
< items
.size(); ++i
) {
85 if (ConvertChangeResourceToResourceEntry(
87 &entries_
[entries_index
],
88 &parent_resource_ids_
[entries_index
])) {
92 entries_
.resize(entries_index
);
93 parent_resource_ids_
.resize(entries_index
);
96 ChangeList::ChangeList(const google_apis::FileList
& file_list
)
97 : next_url_(file_list
.next_link()),
98 largest_changestamp_(0) {
99 const ScopedVector
<google_apis::FileResource
>& items
= file_list
.items();
100 entries_
.resize(items
.size());
101 parent_resource_ids_
.resize(items
.size());
102 size_t entries_index
= 0;
103 for (size_t i
= 0; i
< items
.size(); ++i
) {
104 if (ConvertFileResourceToResourceEntry(
106 &entries_
[entries_index
],
107 &parent_resource_ids_
[entries_index
])) {
111 entries_
.resize(entries_index
);
112 parent_resource_ids_
.resize(entries_index
);
115 ChangeList::~ChangeList() {}
117 ChangeListProcessor::ChangeListProcessor(ResourceMetadata
* resource_metadata
,
118 base::CancellationFlag
* in_shutdown
)
119 : resource_metadata_(resource_metadata
),
120 in_shutdown_(in_shutdown
),
121 changed_files_(new FileChange
) {
124 ChangeListProcessor::~ChangeListProcessor() {
127 FileError
ChangeListProcessor::Apply(
128 scoped_ptr
<google_apis::AboutResource
> about_resource
,
129 ScopedVector
<ChangeList
> change_lists
,
130 bool is_delta_update
) {
131 DCHECK(about_resource
);
133 int64 largest_changestamp
= 0;
134 if (is_delta_update
) {
135 if (!change_lists
.empty()) {
136 // The changestamp appears in the first page of the change list.
137 // The changestamp does not appear in the full resource list.
138 largest_changestamp
= change_lists
[0]->largest_changestamp();
139 DCHECK_GE(change_lists
[0]->largest_changestamp(), 0);
142 largest_changestamp
= about_resource
->largest_change_id();
144 DVLOG(1) << "Root folder ID is " << about_resource
->root_folder_id();
145 DCHECK(!about_resource
->root_folder_id().empty());
148 // Convert ChangeList to map.
149 ChangeListToEntryMapUMAStats uma_stats
;
150 for (size_t i
= 0; i
< change_lists
.size(); ++i
) {
151 ChangeList
* change_list
= change_lists
[i
];
153 std::vector
<ResourceEntry
>* entries
= change_list
->mutable_entries();
154 for (size_t i
= 0; i
< entries
->size(); ++i
) {
155 ResourceEntry
* entry
= &(*entries
)[i
];
157 // Count the number of files.
158 if (!entry
->file_info().is_directory()) {
159 uma_stats
.IncrementNumFiles(
160 entry
->file_specific_info().is_hosted_document());
161 if (entry
->shared_with_me())
162 uma_stats
.IncrementNumSharedWithMeEntries();
164 parent_resource_id_map_
[entry
->resource_id()] =
165 change_list
->parent_resource_ids()[i
];
166 entry_map_
[entry
->resource_id()].Swap(entry
);
167 LOG_IF(WARNING
, !entry
->resource_id().empty())
168 << "Found duplicated file: " << entry
->base_name();
172 // Add the largest changestamp for directories.
173 for (ResourceEntryMap::iterator it
= entry_map_
.begin();
174 it
!= entry_map_
.end(); ++it
) {
175 if (it
->second
.file_info().is_directory()) {
176 it
->second
.mutable_directory_specific_info()->set_changestamp(
177 largest_changestamp
);
181 FileError error
= ApplyEntryMap(largest_changestamp
, about_resource
.Pass());
182 if (error
!= FILE_ERROR_OK
) {
183 DLOG(ERROR
) << "ApplyEntryMap failed: " << FileErrorToString(error
);
187 // Update changestamp.
188 error
= resource_metadata_
->SetLargestChangestamp(largest_changestamp
);
189 if (error
!= FILE_ERROR_OK
) {
190 DLOG(ERROR
) << "SetLargestChangeStamp failed: " << FileErrorToString(error
);
194 // Shouldn't record histograms when processing delta update.
195 if (!is_delta_update
)
196 uma_stats
.UpdateFileCountUmaHistograms();
198 return FILE_ERROR_OK
;
201 FileError
ChangeListProcessor::ApplyEntryMap(
203 scoped_ptr
<google_apis::AboutResource
> about_resource
) {
204 DCHECK(about_resource
);
206 // Create the entry for "My Drive" directory with the latest changestamp.
208 FileError error
= resource_metadata_
->GetResourceEntryByPath(
209 util::GetDriveMyDriveRootPath(), &root
);
210 if (error
!= FILE_ERROR_OK
) {
211 LOG(ERROR
) << "Failed to get root entry: " << FileErrorToString(error
);
215 root
.mutable_directory_specific_info()->set_changestamp(changestamp
);
216 root
.set_resource_id(about_resource
->root_folder_id());
217 error
= resource_metadata_
->RefreshEntry(root
);
218 if (error
!= FILE_ERROR_OK
) {
219 LOG(ERROR
) << "Failed to update root entry: " << FileErrorToString(error
);
223 // Gather the set of changes in the old path.
224 // Note that we want to notify the change in both old and new paths (suppose
225 // /a/b/c is moved to /x/y/c. We want to notify both "/a/b" and "/x/y".)
226 // The old paths must be calculated before we apply any actual changes.
227 // The new paths are calculated after each change is applied. It correctly
228 // sets the new path because we apply changes in such an order (see below).
229 for (ResourceEntryMap::iterator it
= entry_map_
.begin();
230 it
!= entry_map_
.end(); ++it
) {
231 UpdateChangedDirs(it
->second
);
234 // Apply all entries except deleted ones to the metadata.
235 std::vector
<std::string
> deleted_resource_ids
;
236 while (!entry_map_
.empty()) {
237 if (in_shutdown_
&& in_shutdown_
->IsSet())
238 return FILE_ERROR_ABORT
;
240 ResourceEntryMap::iterator it
= entry_map_
.begin();
242 // Process deleted entries later to avoid deleting moved entries under it.
243 if (it
->second
.deleted()) {
244 deleted_resource_ids
.push_back(it
->first
);
245 entry_map_
.erase(it
);
249 // Start from entry_map_.begin() and traverse ancestors using the
250 // parent-child relationships in the result (after this apply) tree.
251 // Then apply the topmost change first.
253 // By doing this, assuming the result tree does not contain any cycles, we
254 // can guarantee that no cycle is made during this apply (i.e. no entry gets
255 // moved under any of its descendants) because the following conditions are
256 // always satisfied in any move:
257 // - The new parent entry is not a descendant of the moved entry.
258 // - The new parent and its ancestors will no longer move during this apply.
259 std::vector
<ResourceEntryMap::iterator
> entries
;
260 for (ResourceEntryMap::iterator it
= entry_map_
.begin();
261 it
!= entry_map_
.end();) {
262 entries
.push_back(it
);
264 DCHECK(parent_resource_id_map_
.count(it
->first
)) << it
->first
;
265 const std::string
& parent_resource_id
=
266 parent_resource_id_map_
[it
->first
];
268 if (parent_resource_id
.empty()) // This entry has no parent.
271 ResourceEntryMap::iterator it_parent
=
272 entry_map_
.find(parent_resource_id
);
273 if (it_parent
== entry_map_
.end()) {
274 // Current entry's parent is already updated or not going to be updated,
275 // get the parent from the local tree.
276 std::string parent_local_id
;
277 FileError error
= resource_metadata_
->GetIdByResourceId(
278 parent_resource_id
, &parent_local_id
);
279 if (error
!= FILE_ERROR_OK
) {
280 // See crbug.com/326043. In some complicated situations, parent folder
281 // for shared entries may be accessible (and hence its resource id is
282 // included), but not in the change/file list.
283 // In such a case, clear the parent and move it to drive/other.
284 if (error
== FILE_ERROR_NOT_FOUND
) {
285 parent_resource_id_map_
[it
->first
] = "";
287 LOG(ERROR
) << "Failed to get local ID: " << parent_resource_id
288 << ", error = " << FileErrorToString(error
);
292 ResourceEntry parent_entry
;
293 while (it_parent
== entry_map_
.end() && !parent_local_id
.empty()) {
294 error
= resource_metadata_
->GetResourceEntryById(
295 parent_local_id
, &parent_entry
);
296 if (error
!= FILE_ERROR_OK
) {
297 LOG(ERROR
) << "Failed to get local entry: "
298 << FileErrorToString(error
);
301 it_parent
= entry_map_
.find(parent_entry
.resource_id());
302 parent_local_id
= parent_entry
.parent_local_id();
308 // Apply the parent first.
309 std::reverse(entries
.begin(), entries
.end());
310 for (size_t i
= 0; i
< entries
.size(); ++i
) {
311 // Skip root entry in the change list. We don't expect servers to send
312 // root entry, but we should better be defensive (see crbug.com/297259).
313 ResourceEntryMap::iterator it
= entries
[i
];
314 if (it
->first
!= root
.resource_id()) {
315 FileError error
= ApplyEntry(it
->second
);
316 if (error
!= FILE_ERROR_OK
) {
317 LOG(ERROR
) << "ApplyEntry failed: " << FileErrorToString(error
)
318 << ", title = " << it
->second
.title();
322 entry_map_
.erase(it
);
326 // Apply deleted entries.
327 for (size_t i
= 0; i
< deleted_resource_ids
.size(); ++i
) {
328 std::string local_id
;
329 FileError error
= resource_metadata_
->GetIdByResourceId(
330 deleted_resource_ids
[i
], &local_id
);
333 error
= resource_metadata_
->RemoveEntry(local_id
);
335 case FILE_ERROR_NOT_FOUND
:
336 error
= FILE_ERROR_OK
;
341 if (error
!= FILE_ERROR_OK
) {
342 LOG(ERROR
) << "Failed to delete: " << FileErrorToString(error
)
343 << ", resource_id = " << deleted_resource_ids
[i
];
348 return FILE_ERROR_OK
;
351 FileError
ChangeListProcessor::ApplyEntry(const ResourceEntry
& entry
) {
352 DCHECK(!entry
.deleted());
353 DCHECK(parent_resource_id_map_
.count(entry
.resource_id()));
354 const std::string
& parent_resource_id
=
355 parent_resource_id_map_
[entry
.resource_id()];
357 ResourceEntry
new_entry(entry
);
358 FileError error
= SetParentLocalIdOfEntry(resource_metadata_
, &new_entry
,
360 if (error
!= FILE_ERROR_OK
)
364 std::string local_id
;
365 error
= resource_metadata_
->GetIdByResourceId(entry
.resource_id(), &local_id
);
367 ResourceEntry existing_entry
;
368 if (error
== FILE_ERROR_OK
)
369 error
= resource_metadata_
->GetResourceEntryById(local_id
, &existing_entry
);
373 if (ShouldApplyChange(existing_entry
, new_entry
)) {
374 // Entry exists and needs to be refreshed.
375 new_entry
.set_local_id(local_id
);
376 // Keep the to-be-synced properties of the existing resource entry.
377 new_entry
.mutable_new_properties()->CopyFrom(
378 existing_entry
.new_properties());
379 error
= resource_metadata_
->RefreshEntry(new_entry
);
381 if (entry
.file_info().is_directory()) {
382 // No need to refresh, but update the changestamp.
383 new_entry
= existing_entry
;
384 new_entry
.mutable_directory_specific_info()->set_changestamp(
385 new_entry
.directory_specific_info().changestamp());
386 error
= resource_metadata_
->RefreshEntry(new_entry
);
388 DVLOG(1) << "Change was discarded for: " << entry
.resource_id();
391 case FILE_ERROR_NOT_FOUND
: { // Adding a new entry.
392 std::string local_id
;
393 error
= resource_metadata_
->AddEntry(new_entry
, &local_id
);
399 if (error
!= FILE_ERROR_OK
)
402 UpdateChangedDirs(entry
);
403 return FILE_ERROR_OK
;
407 FileError
ChangeListProcessor::RefreshDirectory(
408 ResourceMetadata
* resource_metadata
,
409 const DirectoryFetchInfo
& directory_fetch_info
,
410 scoped_ptr
<ChangeList
> change_list
,
411 std::vector
<ResourceEntry
>* out_refreshed_entries
) {
412 DCHECK(!directory_fetch_info
.empty());
414 ResourceEntry directory
;
415 FileError error
= resource_metadata
->GetResourceEntryById(
416 directory_fetch_info
.local_id(), &directory
);
417 if (error
!= FILE_ERROR_OK
)
420 if (!directory
.file_info().is_directory())
421 return FILE_ERROR_NOT_A_DIRECTORY
;
423 std::vector
<ResourceEntry
>* entries
= change_list
->mutable_entries();
424 for (size_t i
= 0; i
< entries
->size(); ++i
) {
425 ResourceEntry
* entry
= &(*entries
)[i
];
426 const std::string
& parent_resource_id
=
427 change_list
->parent_resource_ids()[i
];
429 // Skip if the parent resource ID does not match. This is needed to
430 // handle entries with multiple parents. For such entries, the first
431 // parent is picked and other parents are ignored, hence some entries may
432 // have a parent resource ID which does not match the target directory's.
433 if (parent_resource_id
!= directory_fetch_info
.resource_id()) {
434 DVLOG(1) << "Wrong-parent entry rejected: " << entry
->resource_id();
438 entry
->set_parent_local_id(directory_fetch_info
.local_id());
440 std::string local_id
;
441 error
= resource_metadata
->GetIdByResourceId(entry
->resource_id(),
443 if (error
== FILE_ERROR_OK
) {
444 entry
->set_local_id(local_id
);
445 error
= resource_metadata
->RefreshEntry(*entry
);
448 if (error
== FILE_ERROR_NOT_FOUND
) { // If refreshing fails, try adding.
449 entry
->clear_local_id();
450 error
= resource_metadata
->AddEntry(*entry
, &local_id
);
453 if (error
!= FILE_ERROR_OK
)
456 ResourceEntry result_entry
;
457 error
= resource_metadata
->GetResourceEntryById(local_id
, &result_entry
);
458 if (error
!= FILE_ERROR_OK
)
460 out_refreshed_entries
->push_back(result_entry
);
462 return FILE_ERROR_OK
;
466 FileError
ChangeListProcessor::SetParentLocalIdOfEntry(
467 ResourceMetadata
* resource_metadata
,
468 ResourceEntry
* entry
,
469 const std::string
& parent_resource_id
) {
470 std::string parent_local_id
;
471 if (parent_resource_id
.empty()) {
472 // Entries without parents should go under "other" directory.
473 parent_local_id
= util::kDriveOtherDirLocalId
;
475 FileError error
= resource_metadata
->GetIdByResourceId(
476 parent_resource_id
, &parent_local_id
);
477 if (error
!= FILE_ERROR_OK
)
480 entry
->set_parent_local_id(parent_local_id
);
481 return FILE_ERROR_OK
;
484 void ChangeListProcessor::UpdateChangedDirs(const ResourceEntry
& entry
) {
485 DCHECK(!entry
.resource_id().empty());
487 std::string local_id
;
488 base::FilePath file_path
;
489 if (resource_metadata_
->GetIdByResourceId(
490 entry
.resource_id(), &local_id
) == FILE_ERROR_OK
)
491 resource_metadata_
->GetFilePath(local_id
, &file_path
);
493 if (!file_path
.empty()) {
494 FileChange::ChangeType type
= entry
.deleted()
495 ? FileChange::CHANGE_TYPE_DELETE
496 : FileChange::CHANGE_TYPE_ADD_OR_UPDATE
;
497 changed_files_
->Update(file_path
, entry
, type
);
501 } // namespace internal