diff options
Diffstat (limited to 'apps/saved_files_service.cc')
-rw-r--r-- | apps/saved_files_service.cc | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/apps/saved_files_service.cc b/apps/saved_files_service.cc new file mode 100644 index 0000000..c509c08 --- /dev/null +++ b/apps/saved_files_service.cc @@ -0,0 +1,441 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "apps/saved_files_service.h" + +#include <algorithm> + +#include "apps/saved_files_service_factory.h" +#include "base/basictypes.h" +#include "base/hash_tables.h" +#include "base/value_conversions.h" +#include "chrome/browser/extensions/extension_host.h" +#include "chrome/browser/extensions/extension_prefs.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_system.h" +#include "chrome/common/extensions/permissions/api_permission.h" +#include "chrome/common/extensions/permissions/permission_set.h" + +namespace apps { + +using extensions::APIPermission; +using extensions::Extension; +using extensions::ExtensionHost; +using extensions::ExtensionPrefs; + +namespace { + +// Preference keys + +// The file entries that the app has permission to access. +const char kFileEntries[] = "file_entries"; + +// The path to a file entry that the app had permission to access. +const char kFileEntryPath[] = "path"; + +// Whether or not the app had write access to a file entry. +const char kFileEntryWritable[] = "writable"; + +// The sequence number in the LRU of the file entry. +const char kFileEntrySequenceNumber[] = "sequence_number"; + +const size_t kMaxSavedFileEntries = 500; +const int kMaxSequenceNumber = kint32max; + +// These might be different to the constant values in tests. +size_t g_max_saved_file_entries = kMaxSavedFileEntries; +int g_max_sequence_number = kMaxSequenceNumber; + +// Persists a SavedFileEntry in ExtensionPrefs. +void AddSavedFileEntry(ExtensionPrefs* prefs, + const std::string& extension_id, + const SavedFileEntry& file_entry) { + ExtensionPrefs::ScopedDictionaryUpdate update( + prefs, extension_id, kFileEntries); + DictionaryValue* file_entries = update.Get(); + if (!file_entries) + file_entries = update.Create(); + DCHECK(!file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, NULL)); + + DictionaryValue* file_entry_dict = new DictionaryValue(); + file_entry_dict->Set(kFileEntryPath, CreateFilePathValue(file_entry.path)); + file_entry_dict->SetBoolean(kFileEntryWritable, file_entry.writable); + file_entry_dict->SetInteger(kFileEntrySequenceNumber, + file_entry.sequence_number); + file_entries->SetWithoutPathExpansion(file_entry.id, file_entry_dict); +} + +// Updates the sequence_number of a SavedFileEntry persisted in ExtensionPrefs. +void UpdateSavedFileEntry(ExtensionPrefs* prefs, + const std::string& extension_id, + const SavedFileEntry& file_entry) { + ExtensionPrefs::ScopedDictionaryUpdate update( + prefs, extension_id, kFileEntries); + DictionaryValue* file_entries = update.Get(); + DCHECK(file_entries); + DictionaryValue* file_entry_dict = NULL; + file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, + &file_entry_dict); + DCHECK(file_entry_dict); + file_entry_dict->SetInteger(kFileEntrySequenceNumber, + file_entry.sequence_number); +} + +// Removes a SavedFileEntry from ExtensionPrefs. +void RemoveSavedFileEntry(ExtensionPrefs* prefs, + const std::string& extension_id, + const std::string& file_entry_id) { + ExtensionPrefs::ScopedDictionaryUpdate update( + prefs, extension_id, kFileEntries); + DictionaryValue* file_entries = update.Get(); + if (!file_entries) + file_entries = update.Create(); + file_entries->RemoveWithoutPathExpansion(file_entry_id, NULL); +} + +// Clears all SavedFileEntry for the app from ExtensionPrefs. +void ClearSavedFileEntries(ExtensionPrefs* prefs, + const std::string& extension_id) { + prefs->UpdateExtensionPref(extension_id, kFileEntries, NULL); +} + +// Returns all SavedFileEntries for the app. +std::vector<SavedFileEntry> GetSavedFileEntries( + ExtensionPrefs* prefs, + const std::string& extension_id) { + std::vector<SavedFileEntry> result; + const DictionaryValue* file_entries = NULL; + if (!prefs->ReadPrefAsDictionary(extension_id, kFileEntries, &file_entries)) + return result; + + for (DictionaryValue::Iterator it(*file_entries); !it.IsAtEnd(); + it.Advance()) { + const DictionaryValue* file_entry = NULL; + if (!it.value().GetAsDictionary(&file_entry)) + continue; + const base::Value* path_value; + if (!file_entry->Get(kFileEntryPath, &path_value)) + continue; + base::FilePath file_path; + if (!GetValueAsFilePath(*path_value, &file_path)) + continue; + bool writable = false; + if (!file_entry->GetBoolean(kFileEntryWritable, &writable)) + continue; + int sequence_number = 0; + if (!file_entry->GetInteger(kFileEntrySequenceNumber, &sequence_number)) + continue; + if (!sequence_number) + continue; + result.push_back( + SavedFileEntry(it.key(), file_path, writable, sequence_number)); + } + return result; +} + +} // namespace + +SavedFileEntry::SavedFileEntry() : writable(false), sequence_number(0) {} + +SavedFileEntry::SavedFileEntry(const std::string& id, + const base::FilePath& path, + bool writable, + int sequence_number) + : id(id), + path(path), + writable(writable), + sequence_number(sequence_number) {} + +class SavedFilesService::SavedFiles { + public: + SavedFiles(Profile* profile, const std::string& extension_id); + ~SavedFiles(); + + void RegisterFileEntry(const std::string& id, + const base::FilePath& file_path, + bool writable); + void EnqueueFileEntry(const std::string& id); + bool IsRegistered(const std::string& id) const; + const SavedFileEntry* GetFileEntry(const std::string& id) const; + std::vector<SavedFileEntry> GetAllFileEntries() const; + + private: + // Compacts sequence numbers if the largest sequence number is + // g_max_sequence_number. Outside of testing, it is set to kint32max, so this + // will almost never do any real work. + void MaybeCompactSequenceNumbers(); + + void LoadSavedFileEntriesFromPreferences(); + + Profile* profile_; + const std::string extension_id_; + + // Contains all file entries that have been registered, keyed by ID. Owns + // values. + base::hash_map<std::string, SavedFileEntry*> registered_file_entries_; + STLValueDeleter<base::hash_map<std::string, SavedFileEntry*> > + registered_file_entries_deleter_; + + // The queue of file entries that have been retained, keyed by + // sequence_number. Values are a subset of values in registered_file_entries_. + // This should be kept in sync with file entries stored in extension prefs. + std::map<int, SavedFileEntry*> saved_file_lru_; + + DISALLOW_COPY_AND_ASSIGN(SavedFiles); +}; + +// static +SavedFilesService* SavedFilesService::Get(Profile* profile) { + return SavedFilesServiceFactory::GetForProfile(profile); +} + +SavedFilesService::SavedFilesService(Profile* profile) + : extension_id_to_saved_files_deleter_(&extension_id_to_saved_files_), + profile_(profile) { + registrar_.Add(this, + chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED, + content::NotificationService::AllSources()); + registrar_.Add(this, + chrome::NOTIFICATION_APP_TERMINATING, + content::NotificationService::AllSources()); +} + +SavedFilesService::~SavedFilesService() {} + +void SavedFilesService::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case chrome::NOTIFICATION_EXTENSION_HOST_DESTROYED: { + ExtensionHost* host = content::Details<ExtensionHost>(details).ptr(); + const Extension* extension = host->extension(); + if (extension) { + ClearQueueIfNoRetainPermission(extension); + Clear(extension->id()); + } + break; + } + + case chrome::NOTIFICATION_APP_TERMINATING: { + // Stop listening to NOTIFICATION_EXTENSION_HOST_DESTROYED in particular + // as all extension hosts will be destroyed as a result of shutdown. + registrar_.RemoveAll(); + break; + } + } +} + +void SavedFilesService::RegisterFileEntry(const std::string& extension_id, + const std::string& id, + const base::FilePath& file_path, + bool writable) { + GetOrInsert(extension_id)->RegisterFileEntry(id, file_path, writable); +} + +void SavedFilesService::EnqueueFileEntry(const std::string& extension_id, + const std::string& id) { + GetOrInsert(extension_id)->EnqueueFileEntry(id); +} + +std::vector<SavedFileEntry> SavedFilesService::GetAllFileEntries( + const std::string& extension_id) { + return GetOrInsert(extension_id)->GetAllFileEntries(); +} + +bool SavedFilesService::IsRegistered(const std::string& extension_id, + const std::string& id) { + return GetOrInsert(extension_id)->IsRegistered(id); +} + +const SavedFileEntry* SavedFilesService::GetFileEntry( + const std::string& extension_id, + const std::string& id) { + return GetOrInsert(extension_id)->GetFileEntry(id); +} + +void SavedFilesService::ClearQueueIfNoRetainPermission( + const Extension* extension) { + if (!extension->GetActivePermissions()->HasAPIPermission( + APIPermission::kFileSystemRetainFiles)) { + ClearSavedFileEntries(ExtensionPrefs::Get(profile_), extension->id()); + Clear(extension->id()); + } +} + +SavedFilesService::SavedFiles* SavedFilesService::GetOrInsert( + const std::string& extension_id) { + std::map<std::string, SavedFiles*>::iterator it = + extension_id_to_saved_files_.find(extension_id); + if (it != extension_id_to_saved_files_.end()) + return it->second; + + SavedFiles* saved_files = new SavedFiles(profile_, extension_id); + extension_id_to_saved_files_.insert( + std::make_pair(extension_id, saved_files)); + return saved_files; +} + +void SavedFilesService::Clear(const std::string& extension_id) { + std::map<std::string, SavedFiles*>::iterator it = + extension_id_to_saved_files_.find(extension_id); + if (it != extension_id_to_saved_files_.end()) { + delete it->second; + extension_id_to_saved_files_.erase(it); + } +} + +SavedFilesService::SavedFiles::SavedFiles(Profile* profile, + const std::string& extension_id) + : profile_(profile), + extension_id_(extension_id), + registered_file_entries_deleter_(®istered_file_entries_) { + LoadSavedFileEntriesFromPreferences(); +} + +SavedFilesService::SavedFiles::~SavedFiles() {} + +void SavedFilesService::SavedFiles::RegisterFileEntry( + const std::string& id, + const base::FilePath& file_path, + bool writable) { + if (ContainsKey(registered_file_entries_, id)) + return; + + registered_file_entries_.insert( + std::make_pair(id, new SavedFileEntry(id, file_path, writable, 0))); +} + +void SavedFilesService::SavedFiles::EnqueueFileEntry(const std::string& id) { + base::hash_map<std::string, SavedFileEntry*>::iterator it = + registered_file_entries_.find(id); + DCHECK(it != registered_file_entries_.end()); + + SavedFileEntry* file_entry = it->second; + int old_sequence_number = file_entry->sequence_number; + if (!saved_file_lru_.empty()) { + // Get the sequence number after the last file entry in the LRU. + std::map<int, SavedFileEntry*>::reverse_iterator it = + saved_file_lru_.rbegin(); + if (it->second == file_entry) + return; + + file_entry->sequence_number = it->first + 1; + } else { + // The first sequence number is 1, as 0 means the entry is not in the LRU. + file_entry->sequence_number = 1; + } + saved_file_lru_.insert( + std::make_pair(file_entry->sequence_number, file_entry)); + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); + if (old_sequence_number) { + saved_file_lru_.erase(old_sequence_number); + UpdateSavedFileEntry(prefs, extension_id_, *file_entry); + } else { + AddSavedFileEntry(prefs, extension_id_, *file_entry); + if (saved_file_lru_.size() > g_max_saved_file_entries) { + std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin(); + it->second->sequence_number = 0; + RemoveSavedFileEntry(prefs, extension_id_, it->second->id); + saved_file_lru_.erase(it); + } + } + MaybeCompactSequenceNumbers(); +} + +bool SavedFilesService::SavedFiles::IsRegistered(const std::string& id) const { + return ContainsKey(registered_file_entries_, id); +} + +const SavedFileEntry* SavedFilesService::SavedFiles::GetFileEntry( + const std::string& id) const { + base::hash_map<std::string, SavedFileEntry*>::const_iterator it = + registered_file_entries_.find(id); + if (it == registered_file_entries_.end()) + return NULL; + + return it->second; +} + +std::vector<SavedFileEntry> SavedFilesService::SavedFiles::GetAllFileEntries() + const { + std::vector<SavedFileEntry> result; + for (base::hash_map<std::string, SavedFileEntry*>::const_iterator it = + registered_file_entries_.begin(); + it != registered_file_entries_.end(); + ++it) { + result.push_back(*it->second); + } + return result; +} + +void SavedFilesService::SavedFiles::MaybeCompactSequenceNumbers() { + DCHECK_GE(g_max_sequence_number, 0); + DCHECK_GE(static_cast<size_t>(g_max_sequence_number), + g_max_saved_file_entries); + std::map<int, SavedFileEntry*>::reverse_iterator it = + saved_file_lru_.rbegin(); + if (it == saved_file_lru_.rend()) + return; + + // Only compact sequence numbers if the last entry's sequence number is the + // maximum value. This should almost never be the case. + if (it->first < g_max_sequence_number) + return; + + int sequence_number = 0; + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); + for (std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin(); + it != saved_file_lru_.end(); + ++it) { + sequence_number++; + if (it->second->sequence_number == sequence_number) + continue; + + SavedFileEntry* file_entry = it->second; + file_entry->sequence_number = sequence_number; + UpdateSavedFileEntry(prefs, extension_id_, *file_entry); + saved_file_lru_.erase(it++); + // Provide the following element as an insert hint. While optimized + // insertion time with the following element as a hint is only supported by + // the spec in C++11, the implementations do support this. + it = saved_file_lru_.insert( + it, std::make_pair(file_entry->sequence_number, file_entry)); + } +} + +void SavedFilesService::SavedFiles::LoadSavedFileEntriesFromPreferences() { + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); + std::vector<SavedFileEntry> saved_entries = + GetSavedFileEntries(prefs, extension_id_); + for (std::vector<SavedFileEntry>::iterator it = saved_entries.begin(); + it != saved_entries.end(); + ++it) { + SavedFileEntry* file_entry = new SavedFileEntry(*it); + registered_file_entries_.insert(std::make_pair(file_entry->id, file_entry)); + saved_file_lru_.insert( + std::make_pair(file_entry->sequence_number, file_entry)); + } +} + +// static +void SavedFilesService::SetMaxSequenceNumberForTest(int max_value) { + g_max_sequence_number = max_value; +} + +// static +void SavedFilesService::ClearMaxSequenceNumberForTest() { + g_max_sequence_number = kMaxSequenceNumber; +} + +// static +void SavedFilesService::SetLruSizeForTest(int size) { + g_max_saved_file_entries = size; +} + +// static +void SavedFilesService::ClearLruSizeForTest() { + g_max_saved_file_entries = kMaxSavedFileEntries; +} + +} // namespace apps |