summaryrefslogtreecommitdiffstats
path: root/apps/saved_files_service.cc
diff options
context:
space:
mode:
Diffstat (limited to 'apps/saved_files_service.cc')
-rw-r--r--apps/saved_files_service.cc441
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_(&registered_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