change_list_processor.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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 "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" 15 16namespace drive { 17namespace internal { 18 19namespace { 20 21class ChangeListToEntryMapUMAStats { 22 public: 23 ChangeListToEntryMapUMAStats() 24 : num_regular_files_(0), 25 num_hosted_documents_(0), 26 num_shared_with_me_entries_(0) { 27 } 28 29 // Increments number of files. 30 void IncrementNumFiles(bool is_hosted_document) { 31 is_hosted_document ? num_hosted_documents_++ : num_regular_files_++; 32 } 33 34 // Increments number of shared-with-me entries. 35 void IncrementNumSharedWithMeEntries() { 36 num_shared_with_me_entries_++; 37 } 38 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_); 48 } 49 50 private: 51 int num_regular_files_; 52 int num_hosted_documents_; 53 int num_shared_with_me_entries_; 54}; 55 56} // namespace 57 58std::string DirectoryFetchInfo::ToString() const { 59 return ("resource_id: " + resource_id_ + 60 ", changestamp: " + base::Int64ToString(changestamp_)); 61} 62 63ChangeList::ChangeList() {} 64 65ChangeList::ChangeList(const google_apis::ResourceList& resource_list) 66 : largest_changestamp_(resource_list.largest_changestamp()) { 67 resource_list.GetNextFeedURL(&next_url_); 68 69 entries_.resize(resource_list.entries().size()); 70 parent_resource_ids_.resize(resource_list.entries().size()); 71 size_t entries_index = 0; 72 for (size_t i = 0; i < resource_list.entries().size(); ++i) { 73 if (ConvertToResourceEntry(*resource_list.entries()[i], 74 &entries_[entries_index], 75 &parent_resource_ids_[entries_index])) 76 ++entries_index; 77 } 78 entries_.resize(entries_index); 79 parent_resource_ids_.resize(entries_index); 80} 81 82ChangeList::~ChangeList() {} 83 84ChangeListProcessor::ChangeListProcessor(ResourceMetadata* resource_metadata) 85 : resource_metadata_(resource_metadata) { 86} 87 88ChangeListProcessor::~ChangeListProcessor() { 89} 90 91FileError ChangeListProcessor::Apply( 92 scoped_ptr<google_apis::AboutResource> about_resource, 93 ScopedVector<ChangeList> change_lists, 94 bool is_delta_update) { 95 DCHECK(about_resource); 96 97 int64 largest_changestamp = 0; 98 if (is_delta_update) { 99 if (!change_lists.empty()) { 100 // The changestamp appears in the first page of the change list. 101 // The changestamp does not appear in the full resource list. 102 largest_changestamp = change_lists[0]->largest_changestamp(); 103 DCHECK_GE(change_lists[0]->largest_changestamp(), 0); 104 } 105 } else { 106 largest_changestamp = about_resource->largest_change_id(); 107 108 DVLOG(1) << "Root folder ID is " << about_resource->root_folder_id(); 109 DCHECK(!about_resource->root_folder_id().empty()); 110 } 111 112 // Convert ChangeList to map. 113 ChangeListToEntryMapUMAStats uma_stats; 114 for (size_t i = 0; i < change_lists.size(); ++i) { 115 ChangeList* change_list = change_lists[i]; 116 117 std::vector<ResourceEntry>* entries = change_list->mutable_entries(); 118 for (size_t i = 0; i < entries->size(); ++i) { 119 ResourceEntry* entry = &(*entries)[i]; 120 121 // Count the number of files. 122 if (!entry->file_info().is_directory()) { 123 uma_stats.IncrementNumFiles( 124 entry->file_specific_info().is_hosted_document()); 125 if (entry->shared_with_me()) 126 uma_stats.IncrementNumSharedWithMeEntries(); 127 } 128 parent_resource_id_map_[entry->resource_id()] = 129 change_list->parent_resource_ids()[i]; 130 entry_map_[entry->resource_id()].Swap(entry); 131 LOG_IF(WARNING, !entry->resource_id().empty()) 132 << "Found duplicated file: " << entry->base_name(); 133 } 134 } 135 136 // Add the largest changestamp for directories. 137 for (ResourceEntryMap::iterator it = entry_map_.begin(); 138 it != entry_map_.end(); ++it) { 139 if (it->second.file_info().is_directory()) { 140 it->second.mutable_directory_specific_info()->set_changestamp( 141 largest_changestamp); 142 } 143 } 144 145 FileError error = ApplyEntryMap(largest_changestamp, about_resource.Pass()); 146 if (error != FILE_ERROR_OK) { 147 DLOG(ERROR) << "ApplyEntryMap failed: " << FileErrorToString(error); 148 return error; 149 } 150 151 // Update changestamp. 152 error = resource_metadata_->SetLargestChangestamp(largest_changestamp); 153 if (error != FILE_ERROR_OK) { 154 DLOG(ERROR) << "SetLargestChangeStamp failed: " << FileErrorToString(error); 155 return error; 156 } 157 158 // Shouldn't record histograms when processing delta update. 159 if (!is_delta_update) 160 uma_stats.UpdateFileCountUmaHistograms(); 161 162 return FILE_ERROR_OK; 163} 164 165FileError ChangeListProcessor::ApplyEntryMap( 166 int64 changestamp, 167 scoped_ptr<google_apis::AboutResource> about_resource) { 168 DCHECK(about_resource); 169 170 // Create the entry for "My Drive" directory with the latest changestamp. 171 ResourceEntry root; 172 FileError error = resource_metadata_->GetResourceEntryByPath( 173 util::GetDriveMyDriveRootPath(), &root); 174 switch (error) { 175 case FILE_ERROR_OK: // Refresh the existing My Drive root entry. 176 root.mutable_directory_specific_info()->set_changestamp(changestamp); 177 error = resource_metadata_->RefreshEntry(root); 178 break; 179 case FILE_ERROR_NOT_FOUND: { // Add the My Drive root directory. 180 changed_dirs_.insert(util::GetDriveGrandRootPath()); 181 changed_dirs_.insert(util::GetDriveMyDriveRootPath()); 182 183 root = util::CreateMyDriveRootEntry(about_resource->root_folder_id()); 184 root.mutable_directory_specific_info()->set_changestamp(changestamp); 185 std::string local_id; 186 error = resource_metadata_->AddEntry(root, &local_id); 187 break; 188 } 189 default: 190 break; 191 } 192 if (error != FILE_ERROR_OK) { 193 LOG(ERROR) << "Failed to update root entry: " << FileErrorToString(error); 194 return error; 195 } 196 197 // Gather the set of changes in the old path. 198 // Note that we want to notify the change in both old and new paths (suppose 199 // /a/b/c is moved to /x/y/c. We want to notify both "/a/b" and "/x/y".) 200 // The old paths must be calculated before we apply any actual changes. 201 // The new paths are calculated after each change is applied. It correctly 202 // sets the new path because we apply changes in such an order (see below). 203 for (ResourceEntryMap::iterator it = entry_map_.begin(); 204 it != entry_map_.end(); ++it) { 205 UpdateChangedDirs(it->second); 206 } 207 208 // Apply all entries except deleted ones to the metadata. 209 std::vector<ResourceEntry> deleted_entries; 210 deleted_entries.reserve(entry_map_.size()); 211 while (!entry_map_.empty()) { 212 ResourceEntryMap::iterator it = entry_map_.begin(); 213 214 // Process deleted entries later to avoid deleting moved entries under it. 215 if (it->second.deleted()) { 216 deleted_entries.push_back(ResourceEntry()); 217 deleted_entries.back().Swap(&it->second); 218 entry_map_.erase(it); 219 continue; 220 } 221 222 // Start from entry_map_.begin() and traverse ancestors using the 223 // parent-child relationships in the result (after this apply) tree. 224 // Then apply the topmost change first. 225 // 226 // By doing this, assuming the result tree does not contain any cycles, we 227 // can guarantee that no cycle is made during this apply (i.e. no entry gets 228 // moved under any of its descendants) because the following conditions are 229 // always satisfied in any move: 230 // - The new parent entry is not a descendant of the moved entry. 231 // - The new parent and its ancestors will no longer move during this apply. 232 std::vector<ResourceEntryMap::iterator> entries; 233 for (ResourceEntryMap::iterator it = entry_map_.begin(); 234 it != entry_map_.end();) { 235 entries.push_back(it); 236 237 const std::string& parent_resource_id = 238 parent_resource_id_map_[it->first]; 239 DCHECK(!parent_resource_id.empty()) << it->first; 240 241 ResourceEntryMap::iterator it_parent = 242 entry_map_.find(parent_resource_id); 243 if (it_parent == entry_map_.end()) { 244 // Current entry's parent is already updated or not going to be updated, 245 // get the parent from the local tree. 246 std::string parent_local_id; 247 FileError error = resource_metadata_->GetIdByResourceId( 248 parent_resource_id, &parent_local_id); 249 if (error != FILE_ERROR_OK) { 250 LOG(ERROR) << "Failed to get local ID: " << parent_resource_id 251 << ", error = " << FileErrorToString(error); 252 break; 253 } 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); 261 break; 262 } 263 it_parent = entry_map_.find(parent_entry.resource_id()); 264 parent_local_id = parent_entry.parent_local_id(); 265 } 266 } 267 it = it_parent; 268 } 269 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(); 282 } 283 entry_map_.erase(it); 284 } 285 } 286 287 // Apply deleted entries. 288 for (size_t i = 0; i < deleted_entries.size(); ++i) { 289 // TODO(hashimoto): Handle ApplyEntry errors correctly. 290 FileError error = ApplyEntry(deleted_entries[i]); 291 DLOG_IF(WARNING, error != FILE_ERROR_OK) 292 << "ApplyEntry failed: " << FileErrorToString(error) 293 << ", title = " << deleted_entries[i].title(); 294 } 295 296 return FILE_ERROR_OK; 297} 298 299FileError ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) { 300 // Lookup the entry. 301 std::string local_id; 302 FileError error = resource_metadata_->GetIdByResourceId(entry.resource_id(), 303 &local_id); 304 ResourceEntry existing_entry; 305 if (error == FILE_ERROR_OK) 306 error = resource_metadata_->GetResourceEntryById(local_id, &existing_entry); 307 308 const FileError get_existing_entry_result = error; 309 if (entry.deleted()) { 310 // Deleted file/directory. 311 switch (get_existing_entry_result) { 312 case FILE_ERROR_OK: 313 error = resource_metadata_->RemoveEntry(local_id); 314 break; 315 case FILE_ERROR_NOT_FOUND: // Already deleted. 316 error = FILE_ERROR_OK; 317 break; 318 default: 319 error = get_existing_entry_result; 320 } 321 } else { 322 const std::string& parent_resource_id = 323 parent_resource_id_map_[entry.resource_id()]; 324 DCHECK(!parent_resource_id.empty()) << entry.resource_id(); 325 326 std::string parent_local_id; 327 error = resource_metadata_->GetIdByResourceId( 328 parent_resource_id, &parent_local_id); 329 if (error == FILE_ERROR_OK) { 330 ResourceEntry new_entry(entry); 331 new_entry.set_parent_local_id(parent_local_id); 332 333 switch (get_existing_entry_result) { 334 case FILE_ERROR_OK: // Entry exists and needs to be refreshed. 335 new_entry.set_local_id(local_id); 336 error = resource_metadata_->RefreshEntry(new_entry); 337 break; 338 case FILE_ERROR_NOT_FOUND: { // Adding a new entry. 339 std::string local_id; 340 error = resource_metadata_->AddEntry(new_entry, &local_id); 341 break; 342 } 343 default: 344 error = get_existing_entry_result; 345 } 346 347 if (error == FILE_ERROR_OK) 348 UpdateChangedDirs(entry); 349 } 350 } 351 352 return error; 353} 354 355// static 356FileError ChangeListProcessor::RefreshDirectory( 357 ResourceMetadata* resource_metadata, 358 const DirectoryFetchInfo& directory_fetch_info, 359 ScopedVector<ChangeList> change_lists, 360 base::FilePath* out_file_path) { 361 DCHECK(!directory_fetch_info.empty()); 362 363 std::string directory_local_id; 364 FileError error = resource_metadata->GetIdByResourceId( 365 directory_fetch_info.resource_id(), &directory_local_id); 366 if (error != FILE_ERROR_OK) 367 return error; 368 369 ResourceEntry directory; 370 error = resource_metadata->GetResourceEntryById(directory_local_id, 371 &directory); 372 if (error != FILE_ERROR_OK) 373 return error; 374 375 if (!directory.file_info().is_directory()) 376 return FILE_ERROR_NOT_A_DIRECTORY; 377 378 for (size_t i = 0; i < change_lists.size(); ++i) { 379 ChangeList* change_list = change_lists[i]; 380 std::vector<ResourceEntry>* entries = change_list->mutable_entries(); 381 for (size_t i = 0; i < entries->size(); ++i) { 382 ResourceEntry* entry = &(*entries)[i]; 383 const std::string& parent_resource_id = 384 change_list->parent_resource_ids()[i]; 385 386 // Skip if the parent resource ID does not match. This is needed to 387 // handle entries with multiple parents. For such entries, the first 388 // parent is picked and other parents are ignored, hence some entries may 389 // have a parent resource ID which does not match the target directory's. 390 if (parent_resource_id != directory_fetch_info.resource_id()) { 391 DVLOG(1) << "Wrong-parent entry rejected: " << entry->resource_id(); 392 continue; 393 } 394 395 entry->set_parent_local_id(directory_local_id); 396 397 std::string local_id; 398 error = resource_metadata->GetIdByResourceId(entry->resource_id(), 399 &local_id); 400 if (error == FILE_ERROR_OK) { 401 entry->set_local_id(local_id); 402 error = resource_metadata->RefreshEntry(*entry); 403 } 404 405 if (error == FILE_ERROR_NOT_FOUND) { // If refreshing fails, try adding. 406 std::string local_id; 407 entry->clear_local_id(); 408 error = resource_metadata->AddEntry(*entry, &local_id); 409 } 410 411 if (error != FILE_ERROR_OK) 412 return error; 413 } 414 } 415 416 directory.mutable_directory_specific_info()->set_changestamp( 417 directory_fetch_info.changestamp()); 418 error = resource_metadata->RefreshEntry(directory); 419 if (error != FILE_ERROR_OK) 420 return error; 421 422 *out_file_path = resource_metadata->GetFilePath(directory_local_id); 423 return FILE_ERROR_OK; 424} 425 426void ChangeListProcessor::UpdateChangedDirs(const ResourceEntry& entry) { 427 DCHECK(!entry.resource_id().empty()); 428 429 std::string local_id; 430 base::FilePath file_path; 431 if (resource_metadata_->GetIdByResourceId( 432 entry.resource_id(), &local_id) == FILE_ERROR_OK) 433 file_path = resource_metadata_->GetFilePath(local_id); 434 435 if (!file_path.empty()) { 436 // Notify parent. 437 changed_dirs_.insert(file_path.DirName()); 438 439 if (entry.file_info().is_directory()) { 440 // Notify self if entry is a directory. 441 changed_dirs_.insert(file_path); 442 443 // Notify all descendants if it is a directory deletion. 444 if (entry.deleted()) { 445 std::set<base::FilePath> sub_directories; 446 resource_metadata_->GetSubDirectoriesRecursively(local_id, 447 &sub_directories); 448 changed_dirs_.insert(sub_directories.begin(), sub_directories.end()); 449 } 450 } 451 } 452} 453 454} // namespace internal 455} // namespace drive 456