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 "chrome/browser/chromeos/drive/drive.pb.h" 9 #include "chrome/browser/chromeos/drive/file_system_util.h" 10 #include "chrome/browser/chromeos/drive/resource_entry_conversion.h" 11 #include "chrome/browser/chromeos/drive/resource_metadata.h" 12 #include "chrome/browser/google_apis/drive_api_parser.h" 13 #include "chrome/browser/google_apis/gdata_wapi_parser.h" 14 15 namespace drive { 16 namespace internal { 17 18 ChangeList::ChangeList(const google_apis::ResourceList& resource_list) 19 : largest_changestamp_(resource_list.largest_changestamp()) { 20 resource_list.GetNextFeedURL(&next_url_); 21 22 entries_.resize(resource_list.entries().size()); 23 size_t entries_index = 0; 24 for (size_t i = 0; i < resource_list.entries().size(); ++i) { 25 if (ConvertToResourceEntry(*resource_list.entries()[i], 26 &entries_[entries_index])) 27 ++entries_index; 28 } 29 entries_.resize(entries_index); 30 } 31 32 ChangeList::~ChangeList() {} 33 34 class ChangeListProcessor::ChangeListToEntryMapUMAStats { 35 public: 36 ChangeListToEntryMapUMAStats() 37 : num_regular_files_(0), 38 num_hosted_documents_(0), 39 num_shared_with_me_entries_(0) { 40 } 41 42 // Increments number of files. 43 void IncrementNumFiles(bool is_hosted_document) { 44 is_hosted_document ? num_hosted_documents_++ : num_regular_files_++; 45 } 46 47 // Increments number of shared-with-me entries. 48 void IncrementNumSharedWithMeEntries() { 49 num_shared_with_me_entries_++; 50 } 51 52 // Updates UMA histograms with file counts. 53 void UpdateFileCountUmaHistograms() { 54 const int num_total_files = num_hosted_documents_ + num_regular_files_; 55 UMA_HISTOGRAM_COUNTS("Drive.NumberOfRegularFiles", num_regular_files_); 56 UMA_HISTOGRAM_COUNTS("Drive.NumberOfHostedDocuments", 57 num_hosted_documents_); 58 UMA_HISTOGRAM_COUNTS("Drive.NumberOfTotalFiles", num_total_files); 59 UMA_HISTOGRAM_COUNTS("Drive.NumberOfSharedWithMeEntries", 60 num_shared_with_me_entries_); 61 } 62 63 private: 64 int num_regular_files_; 65 int num_hosted_documents_; 66 int num_shared_with_me_entries_; 67 }; 68 69 ChangeListProcessor::ChangeListProcessor(ResourceMetadata* resource_metadata) 70 : resource_metadata_(resource_metadata) { 71 } 72 73 ChangeListProcessor::~ChangeListProcessor() { 74 } 75 76 void ChangeListProcessor::Apply( 77 scoped_ptr<google_apis::AboutResource> about_resource, 78 ScopedVector<ChangeList> change_lists, 79 bool is_delta_update) { 80 DCHECK(is_delta_update || about_resource.get()); 81 82 int64 largest_changestamp = 0; 83 if (is_delta_update) { 84 if (!change_lists.empty()) { 85 // The changestamp appears in the first page of the change list. 86 // The changestamp does not appear in the full resource list. 87 largest_changestamp = change_lists[0]->largest_changestamp(); 88 DCHECK_GE(change_lists[0]->largest_changestamp(), 0); 89 } 90 } else if (about_resource.get()) { 91 largest_changestamp = about_resource->largest_change_id(); 92 93 DVLOG(1) << "Root folder ID is " << about_resource->root_folder_id(); 94 DCHECK(!about_resource->root_folder_id().empty()); 95 } else { 96 // A full update without AboutResouce will have no effective changestamp. 97 NOTREACHED(); 98 } 99 100 ChangeListToEntryMapUMAStats uma_stats; 101 ConvertToMap(change_lists.Pass(), &entry_map_, &uma_stats); 102 103 // Add the largest changestamp for directories. 104 for (ResourceEntryMap::iterator it = entry_map_.begin(); 105 it != entry_map_.end(); ++it) { 106 if (it->second.file_info().is_directory()) { 107 it->second.mutable_directory_specific_info()->set_changestamp( 108 largest_changestamp); 109 } 110 } 111 112 ApplyEntryMap(is_delta_update, about_resource.Pass()); 113 114 // Update the root entry and finish. 115 UpdateRootEntry(largest_changestamp); 116 117 // Update changestamp. 118 FileError error = resource_metadata_->SetLargestChangestamp( 119 largest_changestamp); 120 DLOG_IF(ERROR, error != FILE_ERROR_OK) << "SetLargestChangeStamp failed: " 121 << FileErrorToString(error); 122 123 // Shouldn't record histograms when processing delta update. 124 if (!is_delta_update) 125 uma_stats.UpdateFileCountUmaHistograms(); 126 } 127 128 void ChangeListProcessor::ApplyEntryMap( 129 bool is_delta_update, 130 scoped_ptr<google_apis::AboutResource> about_resource) { 131 if (!is_delta_update) { // Full update. 132 DCHECK(about_resource); 133 134 FileError error = resource_metadata_->Reset(); 135 136 LOG_IF(ERROR, error != FILE_ERROR_OK) << "Failed to reset: " 137 << FileErrorToString(error); 138 139 changed_dirs_.insert(util::GetDriveGrandRootPath()); 140 changed_dirs_.insert(util::GetDriveMyDriveRootPath()); 141 142 // Create the MyDrive root directory. 143 ApplyEntry(util::CreateMyDriveRootEntry(about_resource->root_folder_id())); 144 } 145 146 // Apply all entries to the metadata. 147 while (!entry_map_.empty()) { 148 // Start from entry_map_.begin() and traverse ancestors. 149 std::vector<ResourceEntryMap::iterator> entries; 150 for (ResourceEntryMap::iterator it = entry_map_.begin(); 151 it != entry_map_.end(); 152 it = entry_map_.find(it->second.parent_resource_id())) { 153 DCHECK_EQ(it->first, it->second.resource_id()); 154 entries.push_back(it); 155 } 156 157 // Apply the parent first. 158 std::reverse(entries.begin(), entries.end()); 159 for (size_t i = 0; i < entries.size(); ++i) { 160 ResourceEntryMap::iterator it = entries[i]; 161 ApplyEntry(it->second); 162 entry_map_.erase(it); 163 } 164 } 165 } 166 167 void ChangeListProcessor::ApplyEntry(const ResourceEntry& entry) { 168 // Lookup the entry. 169 ResourceEntry existing_entry; 170 FileError error = resource_metadata_->GetResourceEntryById( 171 entry.resource_id(), &existing_entry); 172 173 if (error == FILE_ERROR_OK) { 174 if (entry.deleted()) { 175 // Deleted file/directory. 176 RemoveEntry(entry); 177 } else { 178 // Entry exists and needs to be refreshed. 179 RefreshEntry(entry); 180 } 181 } else if (error == FILE_ERROR_NOT_FOUND && !entry.deleted()) { 182 // Adding a new entry. 183 AddEntry(entry); 184 } 185 } 186 187 void ChangeListProcessor::AddEntry(const ResourceEntry& entry) { 188 FileError error = resource_metadata_->AddEntry(entry); 189 190 if (error == FILE_ERROR_OK) { 191 base::FilePath file_path = 192 resource_metadata_->GetFilePath(entry.resource_id()); 193 // Notify if a directory has been created. 194 if (entry.file_info().is_directory()) 195 changed_dirs_.insert(file_path); 196 197 // Notify parent. 198 changed_dirs_.insert(file_path.DirName()); 199 } 200 } 201 202 void ChangeListProcessor::RemoveEntry(const ResourceEntry& entry) { 203 std::set<base::FilePath> child_directories; 204 if (entry.file_info().is_directory()) { 205 resource_metadata_->GetChildDirectories(entry.resource_id(), 206 &child_directories); 207 } 208 209 base::FilePath file_path = 210 resource_metadata_->GetFilePath(entry.resource_id()); 211 212 FileError error = resource_metadata_->RemoveEntry(entry.resource_id()); 213 214 if (error == FILE_ERROR_OK) { 215 // Notify parent. 216 changed_dirs_.insert(file_path.DirName()); 217 218 // Notify children, if any. 219 changed_dirs_.insert(child_directories.begin(), child_directories.end()); 220 221 // If entry is a directory, notify self. 222 if (entry.file_info().is_directory()) 223 changed_dirs_.insert(file_path); 224 } 225 } 226 227 void ChangeListProcessor::RefreshEntry(const ResourceEntry& entry) { 228 base::FilePath old_file_path = 229 resource_metadata_->GetFilePath(entry.resource_id()); 230 231 FileError error = resource_metadata_->RefreshEntry(entry); 232 233 if (error == FILE_ERROR_OK) { 234 base::FilePath new_file_path = 235 resource_metadata_->GetFilePath(entry.resource_id()); 236 237 // Notify old parent. 238 changed_dirs_.insert(old_file_path.DirName()); 239 240 // Notify new parent. 241 changed_dirs_.insert(new_file_path.DirName()); 242 243 // Notify self if entry is a directory. 244 if (entry.file_info().is_directory()) { 245 // Notify new self. 246 changed_dirs_.insert(new_file_path); 247 // Notify old self. 248 changed_dirs_.insert(old_file_path); 249 } 250 } 251 } 252 253 // static 254 void ChangeListProcessor::ConvertToMap( 255 ScopedVector<ChangeList> change_lists, 256 ResourceEntryMap* entry_map, 257 ChangeListToEntryMapUMAStats* uma_stats) { 258 for (size_t i = 0; i < change_lists.size(); ++i) { 259 ChangeList* change_list = change_lists[i]; 260 261 std::vector<ResourceEntry>* entries = change_list->mutable_entries(); 262 for (size_t i = 0; i < entries->size(); ++i) { 263 ResourceEntry* entry = &(*entries)[i]; 264 // Some document entries don't map into files (i.e. sites). 265 if (entry->resource_id().empty()) 266 continue; 267 268 // Count the number of files. 269 if (uma_stats) { 270 if (!entry->file_info().is_directory()) { 271 uma_stats->IncrementNumFiles( 272 entry->file_specific_info().is_hosted_document()); 273 } 274 if (entry->shared_with_me()) 275 uma_stats->IncrementNumSharedWithMeEntries(); 276 } 277 278 (*entry_map)[entry->resource_id()].Swap(entry); 279 LOG_IF(WARNING, !entry->resource_id().empty()) 280 << "Found duplicated file: " << entry->base_name(); 281 } 282 } 283 } 284 285 void ChangeListProcessor::UpdateRootEntry(int64 largest_changestamp) { 286 ResourceEntry root; 287 FileError error = resource_metadata_->GetResourceEntryByPath( 288 util::GetDriveMyDriveRootPath(), &root); 289 290 if (error != FILE_ERROR_OK) { 291 // TODO(satorux): Need to trigger recovery if root is corrupt. 292 LOG(WARNING) << "Failed to get the entry for root directory"; 293 return; 294 } 295 296 // The changestamp should always be updated. 297 root.mutable_directory_specific_info()->set_changestamp(largest_changestamp); 298 299 error = resource_metadata_->RefreshEntry(root); 300 301 LOG_IF(WARNING, error != FILE_ERROR_OK) << "Failed to refresh root directory"; 302 } 303 304 } // namespace internal 305 } // namespace drive 306