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<std::string> deleted_resource_ids; 210 while (!entry_map_.empty()) { 211 ResourceEntryMap::iterator it = entry_map_.begin(); 212 213 // Process deleted entries later to avoid deleting moved entries under it. 214 if (it->second.deleted()) { 215 deleted_resource_ids.push_back(it->first); 216 entry_map_.erase(it); 217 continue; 218 } 219 220 // Start from entry_map_.begin() and traverse ancestors using the 221 // parent-child relationships in the result (after this apply) tree. 222 // Then apply the topmost change first. 223 // 224 // By doing this, assuming the result tree does not contain any cycles, we 225 // can guarantee that no cycle is made during this apply (i.e. no entry gets 226 // moved under any of its descendants) because the following conditions are 227 // always satisfied in any move: 228 // - The new parent entry is not a descendant of the moved entry. 229 // - The new parent and its ancestors will no longer move during this apply. 230 std::vector<ResourceEntryMap::iterator> entries; 231 for (ResourceEntryMap::iterator it = entry_map_.begin(); 232 it != entry_map_.end();) { 233 entries.push_back(it); 234 235 const std::string& parent_resource_id = 236 parent_resource_id_map_[it->first]; 237 DCHECK(!parent_resource_id.empty()) << it->first; 238 239 ResourceEntryMap::iterator it_parent = 240 entry_map_.find(parent_resource_id); 241 if (it_parent == entry_map_.end()) { 242 // Current entry's parent is already updated or not going to be updated, 243 // get the parent from the local tree. 244 std::string parent_local_id; 245 FileError error = resource_metadata_->GetIdByResourceId( 246 parent_resource_id, &parent_local_id); 247 if (error != FILE_ERROR_OK) { 248 LOG(ERROR) << "Failed to get local ID: " << parent_resource_id 249 << ", error = " << FileErrorToString(error); 250 break; 251 } 252 ResourceEntry parent_entry; 253 while (it_parent == entry_map_.end() && !parent_local_id.empty()) { 254 error = resource_metadata_->GetResourceEntryById( 255 parent_local_id, &parent_entry); 256 if (error != FILE_ERROR_OK) { 257 LOG(ERROR) << "Failed to get local entry: " 258 << FileErrorToString(error); 259 break; 260 } 261 it_parent = entry_map_.find(parent_entry.resource_id()); 262 parent_local_id = parent_entry.parent_local_id(); 263 } 264 } 265 it = it_parent; 266 } 267 268 // Apply the parent first. 269 std::reverse(entries.begin(), entries.end()); 270 for (size_t i = 0; i < entries.size(); ++i) { 271 // Skip root entry in the change list. We don't expect servers to send 272 // root entry, but we should better be defensive (see crbug.com/297259). 273 ResourceEntryMap::iterator it = entries[i]; 274 if (it->first != root.resource_id()) { 275 // TODO(hashimoto): Handle ApplyEntry errors correctly. 276 FileError error = ApplyEntry(it->second); 277 DLOG_IF(WARNING, error != FILE_ERROR_OK) 278 << "ApplyEntry failed: " << FileErrorToString(error) 279 << ", title = " << it->second.title(); 280 } 281 entry_map_.erase(it); 282 } 283 } 284 285 // Apply deleted entries. 286 for (size_t i = 0; i < deleted_resource_ids.size(); ++i) { 287 std::string local_id; 288 FileError error = resource_metadata_->GetIdByResourceId( 289 deleted_resource_ids[i], &local_id); 290 if (error == FILE_ERROR_OK) 291 error = resource_metadata_->RemoveEntry(local_id); 292 293 DLOG_IF(WARNING, error != FILE_ERROR_OK && error != FILE_ERROR_NOT_FOUND) 294 << "Failed to delete: " << FileErrorToString(error) 295 << ", resource_id = " << deleted_resource_ids[i]; 296 } 297 298 return FILE_ERROR_OK; 299} 300 301FileError ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) { 302 DCHECK(!entry.deleted()); 303 304 const std::string& parent_resource_id = 305 parent_resource_id_map_[entry.resource_id()]; 306 DCHECK(!parent_resource_id.empty()) << entry.resource_id(); 307 308 std::string parent_local_id; 309 FileError error = resource_metadata_->GetIdByResourceId( 310 parent_resource_id, &parent_local_id); 311 if (error != FILE_ERROR_OK) 312 return error; 313 314 // Lookup the entry. 315 std::string local_id; 316 error = resource_metadata_->GetIdByResourceId(entry.resource_id(), &local_id); 317 318 ResourceEntry existing_entry; 319 if (error == FILE_ERROR_OK) 320 error = resource_metadata_->GetResourceEntryById(local_id, &existing_entry); 321 322 ResourceEntry new_entry(entry); 323 new_entry.set_parent_local_id(parent_local_id); 324 325 switch (error) { 326 case FILE_ERROR_OK: // Entry exists and needs to be refreshed. 327 new_entry.set_local_id(local_id); 328 error = resource_metadata_->RefreshEntry(new_entry); 329 break; 330 case FILE_ERROR_NOT_FOUND: { // Adding a new entry. 331 std::string local_id; 332 error = resource_metadata_->AddEntry(new_entry, &local_id); 333 break; 334 } 335 default: 336 return error; 337 } 338 if (error != FILE_ERROR_OK) 339 return error; 340 341 UpdateChangedDirs(entry); 342 return FILE_ERROR_OK; 343} 344 345// static 346FileError ChangeListProcessor::RefreshDirectory( 347 ResourceMetadata* resource_metadata, 348 const DirectoryFetchInfo& directory_fetch_info, 349 ScopedVector<ChangeList> change_lists, 350 base::FilePath* out_file_path) { 351 DCHECK(!directory_fetch_info.empty()); 352 353 std::string directory_local_id; 354 FileError error = resource_metadata->GetIdByResourceId( 355 directory_fetch_info.resource_id(), &directory_local_id); 356 if (error != FILE_ERROR_OK) 357 return error; 358 359 ResourceEntry directory; 360 error = resource_metadata->GetResourceEntryById(directory_local_id, 361 &directory); 362 if (error != FILE_ERROR_OK) 363 return error; 364 365 if (!directory.file_info().is_directory()) 366 return FILE_ERROR_NOT_A_DIRECTORY; 367 368 for (size_t i = 0; i < change_lists.size(); ++i) { 369 ChangeList* change_list = change_lists[i]; 370 std::vector<ResourceEntry>* entries = change_list->mutable_entries(); 371 for (size_t i = 0; i < entries->size(); ++i) { 372 ResourceEntry* entry = &(*entries)[i]; 373 const std::string& parent_resource_id = 374 change_list->parent_resource_ids()[i]; 375 376 // Skip if the parent resource ID does not match. This is needed to 377 // handle entries with multiple parents. For such entries, the first 378 // parent is picked and other parents are ignored, hence some entries may 379 // have a parent resource ID which does not match the target directory's. 380 if (parent_resource_id != directory_fetch_info.resource_id()) { 381 DVLOG(1) << "Wrong-parent entry rejected: " << entry->resource_id(); 382 continue; 383 } 384 385 entry->set_parent_local_id(directory_local_id); 386 387 std::string local_id; 388 error = resource_metadata->GetIdByResourceId(entry->resource_id(), 389 &local_id); 390 if (error == FILE_ERROR_OK) { 391 entry->set_local_id(local_id); 392 error = resource_metadata->RefreshEntry(*entry); 393 } 394 395 if (error == FILE_ERROR_NOT_FOUND) { // If refreshing fails, try adding. 396 std::string local_id; 397 entry->clear_local_id(); 398 error = resource_metadata->AddEntry(*entry, &local_id); 399 } 400 401 if (error != FILE_ERROR_OK) 402 return error; 403 } 404 } 405 406 directory.mutable_directory_specific_info()->set_changestamp( 407 directory_fetch_info.changestamp()); 408 error = resource_metadata->RefreshEntry(directory); 409 if (error != FILE_ERROR_OK) 410 return error; 411 412 *out_file_path = resource_metadata->GetFilePath(directory_local_id); 413 return FILE_ERROR_OK; 414} 415 416void ChangeListProcessor::UpdateChangedDirs(const ResourceEntry& entry) { 417 DCHECK(!entry.resource_id().empty()); 418 419 std::string local_id; 420 base::FilePath file_path; 421 if (resource_metadata_->GetIdByResourceId( 422 entry.resource_id(), &local_id) == FILE_ERROR_OK) 423 file_path = resource_metadata_->GetFilePath(local_id); 424 425 if (!file_path.empty()) { 426 // Notify parent. 427 changed_dirs_.insert(file_path.DirName()); 428 429 if (entry.file_info().is_directory()) { 430 // Notify self if entry is a directory. 431 changed_dirs_.insert(file_path); 432 433 // Notify all descendants if it is a directory deletion. 434 if (entry.deleted()) { 435 std::set<base::FilePath> sub_directories; 436 resource_metadata_->GetSubDirectoriesRecursively(local_id, 437 &sub_directories); 438 changed_dirs_.insert(sub_directories.begin(), sub_directories.end()); 439 } 440 } 441 } 442} 443 444} // namespace internal 445} // namespace drive 446