| OLD | NEW |
| (Empty) |
| 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 "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" | |
| 17 | |
| 18 namespace drive { | |
| 19 namespace internal { | |
| 20 | |
| 21 namespace { | |
| 22 | |
| 23 class ChangeListToEntryMapUMAStats { | |
| 24 public: | |
| 25 ChangeListToEntryMapUMAStats() | |
| 26 : num_regular_files_(0), | |
| 27 num_hosted_documents_(0), | |
| 28 num_shared_with_me_entries_(0) { | |
| 29 } | |
| 30 | |
| 31 // Increments number of files. | |
| 32 void IncrementNumFiles(bool is_hosted_document) { | |
| 33 is_hosted_document ? num_hosted_documents_++ : num_regular_files_++; | |
| 34 } | |
| 35 | |
| 36 // Increments number of shared-with-me entries. | |
| 37 void IncrementNumSharedWithMeEntries() { | |
| 38 num_shared_with_me_entries_++; | |
| 39 } | |
| 40 | |
| 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_); | |
| 50 } | |
| 51 | |
| 52 private: | |
| 53 int num_regular_files_; | |
| 54 int num_hosted_documents_; | |
| 55 int num_shared_with_me_entries_; | |
| 56 }; | |
| 57 | |
| 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) | |
| 62 return true; | |
| 63 return base::Time::FromInternalValue(remote_entry.modification_date()) > | |
| 64 base::Time::FromInternalValue(local_entry.modification_date()); | |
| 65 } | |
| 66 | |
| 67 } // namespace | |
| 68 | |
| 69 std::string DirectoryFetchInfo::ToString() const { | |
| 70 return ("local_id: " + local_id_ + | |
| 71 ", resource_id: " + resource_id_ + | |
| 72 ", changestamp: " + base::Int64ToString(changestamp_)); | |
| 73 } | |
| 74 | |
| 75 ChangeList::ChangeList() {} | |
| 76 | |
| 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( | |
| 86 *items[i], | |
| 87 &entries_[entries_index], | |
| 88 &parent_resource_ids_[entries_index])) { | |
| 89 ++entries_index; | |
| 90 } | |
| 91 } | |
| 92 entries_.resize(entries_index); | |
| 93 parent_resource_ids_.resize(entries_index); | |
| 94 } | |
| 95 | |
| 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( | |
| 105 *items[i], | |
| 106 &entries_[entries_index], | |
| 107 &parent_resource_ids_[entries_index])) { | |
| 108 ++entries_index; | |
| 109 } | |
| 110 } | |
| 111 entries_.resize(entries_index); | |
| 112 parent_resource_ids_.resize(entries_index); | |
| 113 } | |
| 114 | |
| 115 ChangeList::~ChangeList() {} | |
| 116 | |
| 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) { | |
| 122 } | |
| 123 | |
| 124 ChangeListProcessor::~ChangeListProcessor() { | |
| 125 } | |
| 126 | |
| 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); | |
| 132 | |
| 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); | |
| 140 } | |
| 141 } else { | |
| 142 largest_changestamp = about_resource->largest_change_id(); | |
| 143 | |
| 144 DVLOG(1) << "Root folder ID is " << about_resource->root_folder_id(); | |
| 145 DCHECK(!about_resource->root_folder_id().empty()); | |
| 146 } | |
| 147 | |
| 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]; | |
| 152 | |
| 153 std::vector<ResourceEntry>* entries = change_list->mutable_entries(); | |
| 154 for (size_t i = 0; i < entries->size(); ++i) { | |
| 155 ResourceEntry* entry = &(*entries)[i]; | |
| 156 | |
| 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(); | |
| 163 } | |
| 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(); | |
| 169 } | |
| 170 } | |
| 171 | |
| 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); | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 FileError error = ApplyEntryMap(largest_changestamp, about_resource.Pass()); | |
| 182 if (error != FILE_ERROR_OK) { | |
| 183 DLOG(ERROR) << "ApplyEntryMap failed: " << FileErrorToString(error); | |
| 184 return error; | |
| 185 } | |
| 186 | |
| 187 // Update changestamp. | |
| 188 error = resource_metadata_->SetLargestChangestamp(largest_changestamp); | |
| 189 if (error != FILE_ERROR_OK) { | |
| 190 DLOG(ERROR) << "SetLargestChangeStamp failed: " << FileErrorToString(error); | |
| 191 return error; | |
| 192 } | |
| 193 | |
| 194 // Shouldn't record histograms when processing delta update. | |
| 195 if (!is_delta_update) | |
| 196 uma_stats.UpdateFileCountUmaHistograms(); | |
| 197 | |
| 198 return FILE_ERROR_OK; | |
| 199 } | |
| 200 | |
| 201 FileError ChangeListProcessor::ApplyEntryMap( | |
| 202 int64 changestamp, | |
| 203 scoped_ptr<google_apis::AboutResource> about_resource) { | |
| 204 DCHECK(about_resource); | |
| 205 | |
| 206 // Create the entry for "My Drive" directory with the latest changestamp. | |
| 207 ResourceEntry root; | |
| 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); | |
| 212 return error; | |
| 213 } | |
| 214 | |
| 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); | |
| 220 return error; | |
| 221 } | |
| 222 | |
| 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); | |
| 232 } | |
| 233 | |
| 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; | |
| 239 | |
| 240 ResourceEntryMap::iterator it = entry_map_.begin(); | |
| 241 | |
| 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); | |
| 246 continue; | |
| 247 } | |
| 248 | |
| 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. | |
| 252 // | |
| 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); | |
| 263 | |
| 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]; | |
| 267 | |
| 268 if (parent_resource_id.empty()) // This entry has no parent. | |
| 269 break; | |
| 270 | |
| 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] = ""; | |
| 286 } else { | |
| 287 LOG(ERROR) << "Failed to get local ID: " << parent_resource_id | |
| 288 << ", error = " << FileErrorToString(error); | |
| 289 } | |
| 290 break; | |
| 291 } | |
| 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); | |
| 299 break; | |
| 300 } | |
| 301 it_parent = entry_map_.find(parent_entry.resource_id()); | |
| 302 parent_local_id = parent_entry.parent_local_id(); | |
| 303 } | |
| 304 } | |
| 305 it = it_parent; | |
| 306 } | |
| 307 | |
| 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(); | |
| 319 return error; | |
| 320 } | |
| 321 } | |
| 322 entry_map_.erase(it); | |
| 323 } | |
| 324 } | |
| 325 | |
| 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); | |
| 331 switch (error) { | |
| 332 case FILE_ERROR_OK: | |
| 333 error = resource_metadata_->RemoveEntry(local_id); | |
| 334 break; | |
| 335 case FILE_ERROR_NOT_FOUND: | |
| 336 error = FILE_ERROR_OK; | |
| 337 break; | |
| 338 default: | |
| 339 break; | |
| 340 } | |
| 341 if (error != FILE_ERROR_OK) { | |
| 342 LOG(ERROR) << "Failed to delete: " << FileErrorToString(error) | |
| 343 << ", resource_id = " << deleted_resource_ids[i]; | |
| 344 return error; | |
| 345 } | |
| 346 } | |
| 347 | |
| 348 return FILE_ERROR_OK; | |
| 349 } | |
| 350 | |
| 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()]; | |
| 356 | |
| 357 ResourceEntry new_entry(entry); | |
| 358 FileError error = SetParentLocalIdOfEntry(resource_metadata_, &new_entry, | |
| 359 parent_resource_id); | |
| 360 if (error != FILE_ERROR_OK) | |
| 361 return error; | |
| 362 | |
| 363 // Lookup the entry. | |
| 364 std::string local_id; | |
| 365 error = resource_metadata_->GetIdByResourceId(entry.resource_id(), &local_id); | |
| 366 | |
| 367 ResourceEntry existing_entry; | |
| 368 if (error == FILE_ERROR_OK) | |
| 369 error = resource_metadata_->GetResourceEntryById(local_id, &existing_entry); | |
| 370 | |
| 371 switch (error) { | |
| 372 case FILE_ERROR_OK: | |
| 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); | |
| 380 } else { | |
| 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); | |
| 387 } | |
| 388 DVLOG(1) << "Change was discarded for: " << entry.resource_id(); | |
| 389 } | |
| 390 break; | |
| 391 case FILE_ERROR_NOT_FOUND: { // Adding a new entry. | |
| 392 std::string local_id; | |
| 393 error = resource_metadata_->AddEntry(new_entry, &local_id); | |
| 394 break; | |
| 395 } | |
| 396 default: | |
| 397 return error; | |
| 398 } | |
| 399 if (error != FILE_ERROR_OK) | |
| 400 return error; | |
| 401 | |
| 402 UpdateChangedDirs(entry); | |
| 403 return FILE_ERROR_OK; | |
| 404 } | |
| 405 | |
| 406 // static | |
| 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()); | |
| 413 | |
| 414 ResourceEntry directory; | |
| 415 FileError error = resource_metadata->GetResourceEntryById( | |
| 416 directory_fetch_info.local_id(), &directory); | |
| 417 if (error != FILE_ERROR_OK) | |
| 418 return error; | |
| 419 | |
| 420 if (!directory.file_info().is_directory()) | |
| 421 return FILE_ERROR_NOT_A_DIRECTORY; | |
| 422 | |
| 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]; | |
| 428 | |
| 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(); | |
| 435 continue; | |
| 436 } | |
| 437 | |
| 438 entry->set_parent_local_id(directory_fetch_info.local_id()); | |
| 439 | |
| 440 std::string local_id; | |
| 441 error = resource_metadata->GetIdByResourceId(entry->resource_id(), | |
| 442 &local_id); | |
| 443 if (error == FILE_ERROR_OK) { | |
| 444 entry->set_local_id(local_id); | |
| 445 error = resource_metadata->RefreshEntry(*entry); | |
| 446 } | |
| 447 | |
| 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); | |
| 451 } | |
| 452 | |
| 453 if (error != FILE_ERROR_OK) | |
| 454 return error; | |
| 455 | |
| 456 ResourceEntry result_entry; | |
| 457 error = resource_metadata->GetResourceEntryById(local_id, &result_entry); | |
| 458 if (error != FILE_ERROR_OK) | |
| 459 return error; | |
| 460 out_refreshed_entries->push_back(result_entry); | |
| 461 } | |
| 462 return FILE_ERROR_OK; | |
| 463 } | |
| 464 | |
| 465 // static | |
| 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; | |
| 474 } else { | |
| 475 FileError error = resource_metadata->GetIdByResourceId( | |
| 476 parent_resource_id, &parent_local_id); | |
| 477 if (error != FILE_ERROR_OK) | |
| 478 return error; | |
| 479 } | |
| 480 entry->set_parent_local_id(parent_local_id); | |
| 481 return FILE_ERROR_OK; | |
| 482 } | |
| 483 | |
| 484 void ChangeListProcessor::UpdateChangedDirs(const ResourceEntry& entry) { | |
| 485 DCHECK(!entry.resource_id().empty()); | |
| 486 | |
| 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); | |
| 492 | |
| 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); | |
| 498 } | |
| 499 } | |
| 500 | |
| 501 } // namespace internal | |
| 502 } // namespace drive | |
| OLD | NEW |