Index: apps/saved_files_service.cc |
diff --git a/apps/saved_files_service.cc b/apps/saved_files_service.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b3713e3d0ad90fe38bc313d1f5e7fd504bb0e006 |
--- /dev/null |
+++ b/apps/saved_files_service.cc |
@@ -0,0 +1,406 @@ |
+// 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 an extension has permission to access. |
+const char kFileEntries[] = "file_entries"; |
+ |
+// The path to a file entry that an extension had permission to access. |
+const char kFileEntryPath[] = "path"; |
+ |
+// Whether or not an extension 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; |
+ |
+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); |
+} |
+ |
+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); |
+} |
+ |
+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); |
+} |
+ |
+void ClearSavedFileEntries(ExtensionPrefs* prefs, |
+ const std::string& extension_id) { |
+ prefs->UpdateExtensionPref(extension_id, kFileEntries, NULL); |
+} |
+ |
+void GetSavedFileEntries(ExtensionPrefs* prefs, |
+ const std::string& extension_id, |
+ std::vector<SavedFileEntry>* out) { |
+ const DictionaryValue* file_entries = NULL; |
+ if (!prefs->ReadPrefAsDictionary(extension_id, kFileEntries, &file_entries)) |
+ return; |
+ |
+ 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; |
+ out->push_back( |
+ SavedFileEntry(it.key(), file_path, writable, sequence_number)); |
+ } |
+} |
+ |
+} // namespace |
+ |
+class SavedFilesService::SavedFiles { |
+ public: |
+ SavedFiles(Profile* profile, const std::string& extension_id); |
+ ~SavedFiles(); |
+ |
+ void RetainFileEntry(const std::string& id, |
+ const base::FilePath& file_path, |
+ bool writable); |
+ void MoveEntryToFrontOfQueue(const std::string& id); |
+ bool IsRetained(const std::string& id) const; |
+ bool GetFileEntry(const std::string& id, SavedFileEntry* out) const; |
+ std::vector<SavedFileEntry> GetAllFileEntries() const; |
+ |
+ private: |
+ void MaybeCompactSequenceNumbers(); |
+ |
+ Profile* profile_; |
+ const std::string extension_id_; |
+ |
+ // Owns values. |
+ base::hash_map<std::string, SavedFileEntry*> file_id_to_file_entry_map_; |
+ |
+ STLValueDeleter<base::hash_map<std::string, SavedFileEntry*> > |
+ file_id_to_file_entry_map_deleter_; |
+ |
+ // Values are a subset of values in file_id_to_file_entry_map_. |
+ 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) |
+ ClearRetainedFiles(extension); |
+ 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::RetainFileEntry(const std::string& extension_id, |
+ const std::string& id, |
+ const base::FilePath& file_path, |
+ bool writable) { |
+ GetOrInsert(extension_id)->RetainFileEntry(id, file_path, writable); |
+} |
+ |
+void SavedFilesService::MoveEntryToFrontOfQueue(const std::string& extension_id, |
+ const std::string& id) { |
+ GetOrInsert(extension_id)->MoveEntryToFrontOfQueue(id); |
+} |
+ |
+std::vector<SavedFileEntry> SavedFilesService::GetAllFileEntries( |
+ const std::string& extension_id) { |
+ return GetOrInsert(extension_id)->GetAllFileEntries(); |
+} |
+ |
+bool SavedFilesService::IsRetained(const std::string& extension_id, |
+ const std::string& id) { |
+ return GetOrInsert(extension_id)->IsRetained(id); |
+} |
+ |
+bool SavedFilesService::GetFileEntry(const std::string& extension_id, |
+ const std::string& id, |
+ SavedFileEntry* out) { |
+ return GetOrInsert(extension_id)->GetFileEntry(id, out); |
+} |
+ |
+void SavedFilesService::ClearRetainedFiles(const Extension* extension) { |
+ Cleanup(extension->id()); |
+ if (!extension->GetActivePermissions()->HasAPIPermission( |
+ APIPermission::kFileSystemRetainFiles)) { |
+ ClearSavedFileEntries(ExtensionPrefs::Get(profile_), 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::Cleanup(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), |
+ file_id_to_file_entry_map_deleter_(&file_id_to_file_entry_map_) { |
+ std::vector<SavedFileEntry> saved_entries; |
+ ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); |
+ GetSavedFileEntries(prefs, extension_id_, &saved_entries); |
+ for (std::vector<SavedFileEntry>::iterator it = saved_entries.begin(); |
+ it != saved_entries.end(); ++it) { |
+ SavedFileEntry* file_entry = new SavedFileEntry(*it); |
+ file_id_to_file_entry_map_.insert( |
+ std::make_pair(file_entry->id, file_entry)); |
+ saved_file_lru_.insert( |
+ std::make_pair(file_entry->sequence_number, file_entry)); |
+ } |
+} |
+ |
+SavedFilesService::SavedFiles::~SavedFiles() {} |
+ |
+void SavedFilesService::SavedFiles::RetainFileEntry( |
+ const std::string& id, |
+ const base::FilePath& file_path, |
+ bool writable) { |
+ if (ContainsKey(file_id_to_file_entry_map_, id)) |
+ return; |
+ |
+ file_id_to_file_entry_map_.insert( |
+ std::make_pair(id, new SavedFileEntry(id, file_path, writable, 0))); |
+} |
+ |
+void SavedFilesService::SavedFiles::MoveEntryToFrontOfQueue( |
+ const std::string& id) { |
+ base::hash_map<std::string, SavedFileEntry*>::iterator it = |
+ file_id_to_file_entry_map_.find(id); |
+ if (it == file_id_to_file_entry_map_.end()) |
+ return; |
+ |
+ SavedFileEntry* file_entry = it->second; |
+ int old_sequence_number = file_entry->sequence_number; |
+ if (!saved_file_lru_.empty()) { |
+ 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 { |
+ 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::IsRetained(const std::string& id) const { |
+ return ContainsKey(file_id_to_file_entry_map_, id); |
+} |
+ |
+bool SavedFilesService::SavedFiles::GetFileEntry(const std::string& id, |
+ SavedFileEntry* out) const { |
+ base::hash_map<std::string, SavedFileEntry*>::const_iterator it = |
+ file_id_to_file_entry_map_.find(id); |
+ if (it == file_id_to_file_entry_map_.end()) |
+ return false; |
+ |
+ *out = *it->second; |
+ return true; |
+} |
+ |
+std::vector<SavedFileEntry> |
+SavedFilesService::SavedFiles::GetAllFileEntries() const { |
+ std::vector<SavedFileEntry> result; |
+ for (base::hash_map<std::string, SavedFileEntry*>::const_iterator it = |
+ file_id_to_file_entry_map_.begin(); |
+ it != file_id_to_file_entry_map_.end(); ++it) { |
+ result.push_back(*it->second); |
+ } |
+ return result; |
+} |
+ |
+void SavedFilesService::SavedFiles::MaybeCompactSequenceNumbers() { |
+ std::map<int, SavedFileEntry*>::reverse_iterator it = |
+ saved_file_lru_.rbegin(); |
+ if (it == saved_file_lru_.rend()) |
+ return; |
+ |
+ 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); |
+ if (it == saved_file_lru_.begin()) { |
+ saved_file_lru_.erase(it); |
+ it = saved_file_lru_.insert(std::make_pair(file_entry->sequence_number, |
+ file_entry)).first; |
+ } else { |
+ saved_file_lru_.erase(it--); |
+ it = saved_file_lru_.insert( |
+ it, 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 |