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