1 // Copyright 2013 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 "apps/saved_files_service.h" 6 7 #include <algorithm> 8 9 #include "apps/saved_files_service_factory.h" 10 #include "base/basictypes.h" 11 #include "base/containers/hash_tables.h" 12 #include "base/value_conversions.h" 13 #include "chrome/browser/chrome_notification_types.h" 14 #include "chrome/browser/extensions/extension_host.h" 15 #include "chrome/browser/extensions/extension_prefs.h" 16 #include "chrome/browser/extensions/extension_service.h" 17 #include "chrome/browser/extensions/extension_system.h" 18 #include "chrome/browser/profiles/profile.h" 19 #include "content/public/browser/notification_service.h" 20 #include "extensions/common/permissions/api_permission.h" 21 #include "extensions/common/permissions/permission_set.h" 22 23 namespace apps { 24 25 using extensions::APIPermission; 26 using extensions::Extension; 27 using extensions::ExtensionHost; 28 using extensions::ExtensionPrefs; 29 30 namespace { 31 32 // Preference keys 33 34 // The file entries that the app has permission to access. 35 const char kFileEntries[] = "file_entries"; 36 37 // The path to a file entry that the app had permission to access. 38 const char kFileEntryPath[] = "path"; 39 40 // Whether or not the the entry refers to a directory. 41 const char kFileEntryIsDirectory[] = "is_directory"; 42 43 // The sequence number in the LRU of the file entry. 44 const char kFileEntrySequenceNumber[] = "sequence_number"; 45 46 const size_t kMaxSavedFileEntries = 500; 47 const int kMaxSequenceNumber = kint32max; 48 49 // These might be different to the constant values in tests. 50 size_t g_max_saved_file_entries = kMaxSavedFileEntries; 51 int g_max_sequence_number = kMaxSequenceNumber; 52 53 // Persists a SavedFileEntry in ExtensionPrefs. 54 void AddSavedFileEntry(ExtensionPrefs* prefs, 55 const std::string& extension_id, 56 const SavedFileEntry& file_entry) { 57 ExtensionPrefs::ScopedDictionaryUpdate update( 58 prefs, extension_id, kFileEntries); 59 DictionaryValue* file_entries = update.Get(); 60 if (!file_entries) 61 file_entries = update.Create(); 62 DCHECK(!file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, NULL)); 63 64 DictionaryValue* file_entry_dict = new DictionaryValue(); 65 file_entry_dict->Set(kFileEntryPath, CreateFilePathValue(file_entry.path)); 66 file_entry_dict->SetBoolean(kFileEntryIsDirectory, file_entry.is_directory); 67 file_entry_dict->SetInteger(kFileEntrySequenceNumber, 68 file_entry.sequence_number); 69 file_entries->SetWithoutPathExpansion(file_entry.id, file_entry_dict); 70 } 71 72 // Updates the sequence_number of a SavedFileEntry persisted in ExtensionPrefs. 73 void UpdateSavedFileEntry(ExtensionPrefs* prefs, 74 const std::string& extension_id, 75 const SavedFileEntry& file_entry) { 76 ExtensionPrefs::ScopedDictionaryUpdate update( 77 prefs, extension_id, kFileEntries); 78 DictionaryValue* file_entries = update.Get(); 79 DCHECK(file_entries); 80 DictionaryValue* file_entry_dict = NULL; 81 file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, 82 &file_entry_dict); 83 DCHECK(file_entry_dict); 84 file_entry_dict->SetInteger(kFileEntrySequenceNumber, 85 file_entry.sequence_number); 86 } 87 88 // Removes a SavedFileEntry from ExtensionPrefs. 89 void RemoveSavedFileEntry(ExtensionPrefs* prefs, 90 const std::string& extension_id, 91 const std::string& file_entry_id) { 92 ExtensionPrefs::ScopedDictionaryUpdate update( 93 prefs, extension_id, kFileEntries); 94 DictionaryValue* file_entries = update.Get(); 95 if (!file_entries) 96 file_entries = update.Create(); 97 file_entries->RemoveWithoutPathExpansion(file_entry_id, NULL); 98 } 99 100 // Clears all SavedFileEntry for the app from ExtensionPrefs. 101 void ClearSavedFileEntries(ExtensionPrefs* prefs, 102 const std::string& extension_id) { 103 prefs->UpdateExtensionPref(extension_id, kFileEntries, NULL); 104 } 105 106 // Returns all SavedFileEntries for the app. 107 std::vector<SavedFileEntry> GetSavedFileEntries( 108 ExtensionPrefs* prefs, 109 const std::string& extension_id) { 110 std::vector<SavedFileEntry> result; 111 const DictionaryValue* file_entries = NULL; 112 if (!prefs->ReadPrefAsDictionary(extension_id, kFileEntries, &file_entries)) 113 return result; 114 115 for (DictionaryValue::Iterator it(*file_entries); !it.IsAtEnd(); 116 it.Advance()) { 117 const DictionaryValue* file_entry = NULL; 118 if (!it.value().GetAsDictionary(&file_entry)) 119 continue; 120 const base::Value* path_value; 121 if (!file_entry->Get(kFileEntryPath, &path_value)) 122 continue; 123 base::FilePath file_path; 124 if (!GetValueAsFilePath(*path_value, &file_path)) 125 continue; 126 bool is_directory = false; 127 file_entry->GetBoolean(kFileEntryIsDirectory, &is_directory); 128 int sequence_number = 0; 129 if (!file_entry->GetInteger(kFileEntrySequenceNumber, &sequence_number)) 130 continue; 131 if (!sequence_number) 132 continue; 133 result.push_back( 134 SavedFileEntry(it.key(), file_path, is_directory, sequence_number)); 135 } 136 return result; 137 } 138 139 } // namespace 140 141 SavedFileEntry::SavedFileEntry() : is_directory(false), sequence_number(0) {} 142 143 SavedFileEntry::SavedFileEntry(const std::string& id, 144 const base::FilePath& path, 145 bool is_directory, 146 int sequence_number) 147 : id(id), 148 path(path), 149 is_directory(is_directory), 150 sequence_number(sequence_number) {} 151 152 class SavedFilesService::SavedFiles { 153 public: 154 SavedFiles(Profile* profile, const std::string& extension_id); 155 ~SavedFiles(); 156 157 void RegisterFileEntry(const std::string& id, 158 const base::FilePath& file_path, 159 bool is_directory); 160 void EnqueueFileEntry(const std::string& id); 161 bool IsRegistered(const std::string& id) const; 162 const SavedFileEntry* GetFileEntry(const std::string& id) const; 163 std::vector<SavedFileEntry> GetAllFileEntries() const; 164 165 private: 166 // Compacts sequence numbers if the largest sequence number is 167 // g_max_sequence_number. Outside of testing, it is set to kint32max, so this 168 // will almost never do any real work. 169 void MaybeCompactSequenceNumbers(); 170 171 void LoadSavedFileEntriesFromPreferences(); 172 173 Profile* profile_; 174 const std::string extension_id_; 175 176 // Contains all file entries that have been registered, keyed by ID. Owns 177 // values. 178 base::hash_map<std::string, SavedFileEntry*> registered_file_entries_; 179 STLValueDeleter<base::hash_map<std::string, SavedFileEntry*> > 180 registered_file_entries_deleter_; 181 182 // The queue of file entries that have been retained, keyed by 183 // sequence_number. Values are a subset of values in registered_file_entries_. 184 // This should be kept in sync with file entries stored in extension prefs. 185 std::map<int, SavedFileEntry*> saved_file_lru_; 186 187 DISALLOW_COPY_AND_ASSIGN(SavedFiles); 188 }; 189 190 // static 191 SavedFilesService* SavedFilesService::Get(Profile* profile) { 192 return SavedFilesServiceFactory::GetForProfile(profile); 193 } 194 195 SavedFilesService::SavedFilesService(Profile* profile) 196 : extension_id_to_saved_files_deleter_(&extension_id_to_saved_files_), 197 profile_(profile) { 198 registrar_.Add(this, 199 chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, 200 content::NotificationService::AllSources()); 201 registrar_.Add(this, 202 chrome::NOTIFICATION_APP_TERMINATING, 203 content::NotificationService::AllSources()); 204 } 205 206 SavedFilesService::~SavedFilesService() {} 207 208 void SavedFilesService::Observe(int type, 209 const content::NotificationSource& source, 210 const content::NotificationDetails& details) { 211 switch (type) { 212 case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: { 213 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); 214 const Extension* extension = host->extension(); 215 if (extension) { 216 ClearQueueIfNoRetainPermission(extension); 217 Clear(extension->id()); 218 } 219 break; 220 } 221 222 case chrome::NOTIFICATION_APP_TERMINATING: { 223 // Stop listening to NOTIFICATION_EXTENSION_HOST_DESTROYED in particular 224 // as all extension hosts will be destroyed as a result of shutdown. 225 registrar_.RemoveAll(); 226 break; 227 } 228 } 229 } 230 231 void SavedFilesService::RegisterFileEntry(const std::string& extension_id, 232 const std::string& id, 233 const base::FilePath& file_path, 234 bool is_directory) { 235 GetOrInsert(extension_id)->RegisterFileEntry(id, file_path, is_directory); 236 } 237 238 void SavedFilesService::EnqueueFileEntry(const std::string& extension_id, 239 const std::string& id) { 240 GetOrInsert(extension_id)->EnqueueFileEntry(id); 241 } 242 243 std::vector<SavedFileEntry> SavedFilesService::GetAllFileEntries( 244 const std::string& extension_id) { 245 SavedFiles* saved_files = Get(extension_id); 246 if (saved_files) 247 return saved_files->GetAllFileEntries(); 248 return GetSavedFileEntries(ExtensionPrefs::Get(profile_), extension_id); 249 } 250 251 bool SavedFilesService::IsRegistered(const std::string& extension_id, 252 const std::string& id) { 253 return GetOrInsert(extension_id)->IsRegistered(id); 254 } 255 256 const SavedFileEntry* SavedFilesService::GetFileEntry( 257 const std::string& extension_id, 258 const std::string& id) { 259 return GetOrInsert(extension_id)->GetFileEntry(id); 260 } 261 262 void SavedFilesService::ClearQueueIfNoRetainPermission( 263 const Extension* extension) { 264 if (!extension->GetActivePermissions()->HasAPIPermission( 265 APIPermission::kFileSystemRetainEntries)) { 266 ClearQueue(extension); 267 } 268 } 269 270 void SavedFilesService::ClearQueue(const extensions::Extension* extension) { 271 ClearSavedFileEntries(ExtensionPrefs::Get(profile_), extension->id()); 272 Clear(extension->id()); 273 } 274 275 SavedFilesService::SavedFiles* SavedFilesService::Get( 276 const std::string& extension_id) const { 277 std::map<std::string, SavedFiles*>::const_iterator it = 278 extension_id_to_saved_files_.find(extension_id); 279 if (it != extension_id_to_saved_files_.end()) 280 return it->second; 281 282 return NULL; 283 } 284 285 SavedFilesService::SavedFiles* SavedFilesService::GetOrInsert( 286 const std::string& extension_id) { 287 SavedFiles* saved_files = Get(extension_id); 288 if (saved_files) 289 return saved_files; 290 291 saved_files = new SavedFiles(profile_, extension_id); 292 extension_id_to_saved_files_.insert( 293 std::make_pair(extension_id, saved_files)); 294 return saved_files; 295 } 296 297 void SavedFilesService::Clear(const std::string& extension_id) { 298 std::map<std::string, SavedFiles*>::iterator it = 299 extension_id_to_saved_files_.find(extension_id); 300 if (it != extension_id_to_saved_files_.end()) { 301 delete it->second; 302 extension_id_to_saved_files_.erase(it); 303 } 304 } 305 306 SavedFilesService::SavedFiles::SavedFiles(Profile* profile, 307 const std::string& extension_id) 308 : profile_(profile), 309 extension_id_(extension_id), 310 registered_file_entries_deleter_(®istered_file_entries_) { 311 LoadSavedFileEntriesFromPreferences(); 312 } 313 314 SavedFilesService::SavedFiles::~SavedFiles() {} 315 316 void SavedFilesService::SavedFiles::RegisterFileEntry( 317 const std::string& id, 318 const base::FilePath& file_path, 319 bool is_directory) { 320 if (ContainsKey(registered_file_entries_, id)) 321 return; 322 323 registered_file_entries_.insert( 324 std::make_pair(id, new SavedFileEntry(id, file_path, is_directory, 0))); 325 } 326 327 void SavedFilesService::SavedFiles::EnqueueFileEntry(const std::string& id) { 328 base::hash_map<std::string, SavedFileEntry*>::iterator it = 329 registered_file_entries_.find(id); 330 DCHECK(it != registered_file_entries_.end()); 331 332 SavedFileEntry* file_entry = it->second; 333 int old_sequence_number = file_entry->sequence_number; 334 if (!saved_file_lru_.empty()) { 335 // Get the sequence number after the last file entry in the LRU. 336 std::map<int, SavedFileEntry*>::reverse_iterator it = 337 saved_file_lru_.rbegin(); 338 if (it->second == file_entry) 339 return; 340 341 file_entry->sequence_number = it->first + 1; 342 } else { 343 // The first sequence number is 1, as 0 means the entry is not in the LRU. 344 file_entry->sequence_number = 1; 345 } 346 saved_file_lru_.insert( 347 std::make_pair(file_entry->sequence_number, file_entry)); 348 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 349 if (old_sequence_number) { 350 saved_file_lru_.erase(old_sequence_number); 351 UpdateSavedFileEntry(prefs, extension_id_, *file_entry); 352 } else { 353 AddSavedFileEntry(prefs, extension_id_, *file_entry); 354 if (saved_file_lru_.size() > g_max_saved_file_entries) { 355 std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin(); 356 it->second->sequence_number = 0; 357 RemoveSavedFileEntry(prefs, extension_id_, it->second->id); 358 saved_file_lru_.erase(it); 359 } 360 } 361 MaybeCompactSequenceNumbers(); 362 } 363 364 bool SavedFilesService::SavedFiles::IsRegistered(const std::string& id) const { 365 return ContainsKey(registered_file_entries_, id); 366 } 367 368 const SavedFileEntry* SavedFilesService::SavedFiles::GetFileEntry( 369 const std::string& id) const { 370 base::hash_map<std::string, SavedFileEntry*>::const_iterator it = 371 registered_file_entries_.find(id); 372 if (it == registered_file_entries_.end()) 373 return NULL; 374 375 return it->second; 376 } 377 378 std::vector<SavedFileEntry> SavedFilesService::SavedFiles::GetAllFileEntries() 379 const { 380 std::vector<SavedFileEntry> result; 381 for (base::hash_map<std::string, SavedFileEntry*>::const_iterator it = 382 registered_file_entries_.begin(); 383 it != registered_file_entries_.end(); 384 ++it) { 385 result.push_back(*it->second); 386 } 387 return result; 388 } 389 390 void SavedFilesService::SavedFiles::MaybeCompactSequenceNumbers() { 391 DCHECK_GE(g_max_sequence_number, 0); 392 DCHECK_GE(static_cast<size_t>(g_max_sequence_number), 393 g_max_saved_file_entries); 394 std::map<int, SavedFileEntry*>::reverse_iterator it = 395 saved_file_lru_.rbegin(); 396 if (it == saved_file_lru_.rend()) 397 return; 398 399 // Only compact sequence numbers if the last entry's sequence number is the 400 // maximum value. This should almost never be the case. 401 if (it->first < g_max_sequence_number) 402 return; 403 404 int sequence_number = 0; 405 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 406 for (std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin(); 407 it != saved_file_lru_.end(); 408 ++it) { 409 sequence_number++; 410 if (it->second->sequence_number == sequence_number) 411 continue; 412 413 SavedFileEntry* file_entry = it->second; 414 file_entry->sequence_number = sequence_number; 415 UpdateSavedFileEntry(prefs, extension_id_, *file_entry); 416 saved_file_lru_.erase(it++); 417 // Provide the following element as an insert hint. While optimized 418 // insertion time with the following element as a hint is only supported by 419 // the spec in C++11, the implementations do support this. 420 it = saved_file_lru_.insert( 421 it, std::make_pair(file_entry->sequence_number, file_entry)); 422 } 423 } 424 425 void SavedFilesService::SavedFiles::LoadSavedFileEntriesFromPreferences() { 426 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); 427 std::vector<SavedFileEntry> saved_entries = 428 GetSavedFileEntries(prefs, extension_id_); 429 for (std::vector<SavedFileEntry>::iterator it = saved_entries.begin(); 430 it != saved_entries.end(); 431 ++it) { 432 SavedFileEntry* file_entry = new SavedFileEntry(*it); 433 registered_file_entries_.insert(std::make_pair(file_entry->id, file_entry)); 434 saved_file_lru_.insert( 435 std::make_pair(file_entry->sequence_number, file_entry)); 436 } 437 } 438 439 // static 440 void SavedFilesService::SetMaxSequenceNumberForTest(int max_value) { 441 g_max_sequence_number = max_value; 442 } 443 444 // static 445 void SavedFilesService::ClearMaxSequenceNumberForTest() { 446 g_max_sequence_number = kMaxSequenceNumber; 447 } 448 449 // static 450 void SavedFilesService::SetLruSizeForTest(int size) { 451 g_max_saved_file_entries = size; 452 } 453 454 // static 455 void SavedFilesService::ClearLruSizeForTest() { 456 g_max_saved_file_entries = kMaxSavedFileEntries; 457 } 458 459 } // namespace apps 460