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.
5 #include "apps/saved_files_service.h"
12 #include <unordered_map>
15 #include "apps/saved_files_service_factory.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/api/file_system/saved_file_entry.h"
20 #include "extensions/browser/extension_host.h"
21 #include "extensions/browser/extension_prefs.h"
22 #include "extensions/browser/extension_system.h"
23 #include "extensions/browser/notification_types.h"
24 #include "extensions/common/permissions/api_permission.h"
25 #include "extensions/common/permissions/permission_set.h"
26 #include "extensions/common/permissions/permissions_data.h"
30 using extensions::APIPermission;
31 using extensions::Extension;
32 using extensions::ExtensionHost;
33 using extensions::ExtensionPrefs;
34 using extensions::SavedFileEntry;
40 // The file entries that the app has permission to access.
41 const char kFileEntries[] = "file_entries";
43 // The path to a file entry that the app had permission to access.
44 const char kFileEntryPath[] = "path";
46 // Whether or not the the entry refers to a directory.
47 const char kFileEntryIsDirectory[] = "is_directory";
49 // The sequence number in the LRU of the file entry.
50 const char kFileEntrySequenceNumber[] = "sequence_number";
52 const size_t kMaxSavedFileEntries = 500;
53 const int kMaxSequenceNumber = INT32_MAX;
55 // These might be different to the constant values in tests.
56 size_t g_max_saved_file_entries = kMaxSavedFileEntries;
57 int g_max_sequence_number = kMaxSequenceNumber;
59 // Persists a SavedFileEntry in ExtensionPrefs.
60 void AddSavedFileEntry(ExtensionPrefs* prefs,
61 const std::string& extension_id,
62 const SavedFileEntry& file_entry) {
63 ExtensionPrefs::ScopedDictionaryUpdate update(
64 prefs, extension_id, kFileEntries);
65 auto file_entries = update.Create();
66 DCHECK(!file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, NULL));
68 std::unique_ptr<base::DictionaryValue> file_entry_dict =
69 std::make_unique<base::DictionaryValue>();
70 file_entry_dict->SetKey(kFileEntryPath, CreateFilePathValue(file_entry.path));
71 file_entry_dict->SetBoolean(kFileEntryIsDirectory, file_entry.is_directory);
72 file_entry_dict->SetInteger(kFileEntrySequenceNumber,
73 file_entry.sequence_number);
74 file_entries->SetWithoutPathExpansion(file_entry.id,
75 std::move(file_entry_dict));
78 // Updates the sequence_number of a SavedFileEntry persisted in ExtensionPrefs.
79 void UpdateSavedFileEntry(ExtensionPrefs* prefs,
80 const std::string& extension_id,
81 const SavedFileEntry& file_entry) {
82 ExtensionPrefs::ScopedDictionaryUpdate update(
83 prefs, extension_id, kFileEntries);
84 auto file_entries = update.Get();
86 std::unique_ptr<prefs::DictionaryValueUpdate> file_entry_dict;
87 file_entries->GetDictionaryWithoutPathExpansion(file_entry.id,
89 DCHECK(file_entry_dict);
90 file_entry_dict->SetInteger(kFileEntrySequenceNumber,
91 file_entry.sequence_number);
94 // Removes a SavedFileEntry from ExtensionPrefs.
95 void RemoveSavedFileEntry(ExtensionPrefs* prefs,
96 const std::string& extension_id,
97 const std::string& file_entry_id) {
98 ExtensionPrefs::ScopedDictionaryUpdate update(
99 prefs, extension_id, kFileEntries);
100 auto file_entries = update.Create();
101 file_entries->RemoveWithoutPathExpansion(file_entry_id, NULL);
104 // Clears all SavedFileEntry for the app from ExtensionPrefs.
105 void ClearSavedFileEntries(ExtensionPrefs* prefs,
106 const std::string& extension_id) {
107 prefs->UpdateExtensionPref(extension_id, kFileEntries, nullptr);
110 // Returns all SavedFileEntries for the app.
111 std::vector<SavedFileEntry> GetSavedFileEntries(
112 ExtensionPrefs* prefs,
113 const std::string& extension_id) {
114 std::vector<SavedFileEntry> result;
115 const base::DictionaryValue* file_entries = NULL;
116 if (!prefs->ReadPrefAsDictionary(extension_id, kFileEntries, &file_entries))
119 for (base::DictionaryValue::Iterator it(*file_entries); !it.IsAtEnd();
121 const base::DictionaryValue* file_entry = NULL;
122 if (!it.value().GetAsDictionary(&file_entry))
124 const base::Value* path_value;
125 if (!file_entry->Get(kFileEntryPath, &path_value))
127 base::FilePath file_path;
128 if (!GetValueAsFilePath(*path_value, &file_path))
130 bool is_directory = false;
131 file_entry->GetBoolean(kFileEntryIsDirectory, &is_directory);
132 int sequence_number = 0;
133 if (!file_entry->GetInteger(kFileEntrySequenceNumber, &sequence_number))
135 if (!sequence_number)
138 SavedFileEntry(it.key(), file_path, is_directory, sequence_number));
145 class SavedFilesService::SavedFiles {
147 SavedFiles(content::BrowserContext* context, const std::string& extension_id);
150 void RegisterFileEntry(const std::string& id,
151 const base::FilePath& file_path,
153 void EnqueueFileEntry(const std::string& id);
154 bool IsRegistered(const std::string& id) const;
155 const SavedFileEntry* GetFileEntry(const std::string& id) const;
156 std::vector<SavedFileEntry> GetAllFileEntries() const;
159 // Compacts sequence numbers if the largest sequence number is
160 // g_max_sequence_number. Outside of testing, it is set to kint32max, so this
161 // will almost never do any real work.
162 void MaybeCompactSequenceNumbers();
164 void LoadSavedFileEntriesFromPreferences();
166 content::BrowserContext* context_;
167 const std::string extension_id_;
169 // Contains all file entries that have been registered, keyed by ID.
170 std::unordered_map<std::string, std::unique_ptr<SavedFileEntry>>
171 registered_file_entries_;
173 // The queue of file entries that have been retained, keyed by
174 // sequence_number. Values are a subset of values in registered_file_entries_.
175 // This should be kept in sync with file entries stored in extension prefs.
176 std::map<int, SavedFileEntry*> saved_file_lru_;
178 DISALLOW_COPY_AND_ASSIGN(SavedFiles);
182 SavedFilesService* SavedFilesService::Get(content::BrowserContext* context) {
183 return SavedFilesServiceFactory::GetForBrowserContext(context);
186 SavedFilesService::SavedFilesService(content::BrowserContext* context)
187 : context_(context) {
189 extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
190 content::NotificationService::AllSources());
193 SavedFilesService::~SavedFilesService() = default;
195 void SavedFilesService::Observe(int type,
196 const content::NotificationSource& source,
197 const content::NotificationDetails& details) {
198 DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED, type);
199 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
200 const Extension* extension = host->extension();
202 ClearQueueIfNoRetainPermission(extension);
203 Clear(extension->id());
207 void SavedFilesService::RegisterFileEntry(const std::string& extension_id,
208 const std::string& id,
209 const base::FilePath& file_path,
211 GetOrInsert(extension_id)->RegisterFileEntry(id, file_path, is_directory);
214 void SavedFilesService::EnqueueFileEntry(const std::string& extension_id,
215 const std::string& id) {
216 GetOrInsert(extension_id)->EnqueueFileEntry(id);
219 std::vector<SavedFileEntry> SavedFilesService::GetAllFileEntries(
220 const std::string& extension_id) {
221 SavedFiles* saved_files = Get(extension_id);
223 return saved_files->GetAllFileEntries();
224 return GetSavedFileEntries(ExtensionPrefs::Get(context_), extension_id);
227 bool SavedFilesService::IsRegistered(const std::string& extension_id,
228 const std::string& id) {
229 return GetOrInsert(extension_id)->IsRegistered(id);
232 const SavedFileEntry* SavedFilesService::GetFileEntry(
233 const std::string& extension_id,
234 const std::string& id) {
235 return GetOrInsert(extension_id)->GetFileEntry(id);
238 void SavedFilesService::ClearQueueIfNoRetainPermission(
239 const Extension* extension) {
240 if (!extension->permissions_data()->active_permissions().HasAPIPermission(
241 APIPermission::kFileSystemRetainEntries)) {
242 ClearQueue(extension);
246 void SavedFilesService::ClearQueue(const extensions::Extension* extension) {
247 ClearSavedFileEntries(ExtensionPrefs::Get(context_), extension->id());
248 Clear(extension->id());
251 void SavedFilesService::OnApplicationTerminating() {
252 // Stop listening to NOTIFICATION_EXTENSION_HOST_DESTROYED in particular
253 // as all extension hosts will be destroyed as a result of shutdown.
254 registrar_.RemoveAll();
257 SavedFilesService::SavedFiles* SavedFilesService::Get(
258 const std::string& extension_id) const {
259 auto it = extension_id_to_saved_files_.find(extension_id);
260 if (it != extension_id_to_saved_files_.end())
261 return it->second.get();
266 SavedFilesService::SavedFiles* SavedFilesService::GetOrInsert(
267 const std::string& extension_id) {
268 SavedFiles* saved_files = Get(extension_id);
272 std::unique_ptr<SavedFiles> scoped_saved_files(
273 new SavedFiles(context_, extension_id));
274 saved_files = scoped_saved_files.get();
275 extension_id_to_saved_files_.insert(
276 std::make_pair(extension_id, std::move(scoped_saved_files)));
280 void SavedFilesService::Clear(const std::string& extension_id) {
281 extension_id_to_saved_files_.erase(extension_id);
284 SavedFilesService::SavedFiles::SavedFiles(content::BrowserContext* context,
285 const std::string& extension_id)
286 : context_(context), extension_id_(extension_id) {
287 LoadSavedFileEntriesFromPreferences();
290 SavedFilesService::SavedFiles::~SavedFiles() = default;
292 void SavedFilesService::SavedFiles::RegisterFileEntry(
293 const std::string& id,
294 const base::FilePath& file_path,
296 auto it = registered_file_entries_.find(id);
297 if (it != registered_file_entries_.end())
300 registered_file_entries_[id] =
301 std::make_unique<SavedFileEntry>(id, file_path, is_directory, 0);
304 void SavedFilesService::SavedFiles::EnqueueFileEntry(const std::string& id) {
305 auto it = registered_file_entries_.find(id);
306 DCHECK(it != registered_file_entries_.end());
308 SavedFileEntry* file_entry = it->second.get();
309 int old_sequence_number = file_entry->sequence_number;
310 if (!saved_file_lru_.empty()) {
311 // Get the sequence number after the last file entry in the LRU.
312 std::map<int, SavedFileEntry*>::reverse_iterator it =
313 saved_file_lru_.rbegin();
314 if (it->second == file_entry)
317 file_entry->sequence_number = it->first + 1;
319 // The first sequence number is 1, as 0 means the entry is not in the LRU.
320 file_entry->sequence_number = 1;
322 saved_file_lru_.insert(
323 std::make_pair(file_entry->sequence_number, file_entry));
324 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
325 if (old_sequence_number) {
326 saved_file_lru_.erase(old_sequence_number);
327 UpdateSavedFileEntry(prefs, extension_id_, *file_entry);
329 AddSavedFileEntry(prefs, extension_id_, *file_entry);
330 if (saved_file_lru_.size() > g_max_saved_file_entries) {
331 std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin();
332 it->second->sequence_number = 0;
333 RemoveSavedFileEntry(prefs, extension_id_, it->second->id);
334 saved_file_lru_.erase(it);
337 MaybeCompactSequenceNumbers();
340 bool SavedFilesService::SavedFiles::IsRegistered(const std::string& id) const {
341 auto it = registered_file_entries_.find(id);
342 return it != registered_file_entries_.end();
345 const SavedFileEntry* SavedFilesService::SavedFiles::GetFileEntry(
346 const std::string& id) const {
347 auto it = registered_file_entries_.find(id);
348 if (it == registered_file_entries_.end())
351 return it->second.get();
354 std::vector<SavedFileEntry> SavedFilesService::SavedFiles::GetAllFileEntries()
356 std::vector<SavedFileEntry> result;
357 for (auto it = registered_file_entries_.begin();
358 it != registered_file_entries_.end(); ++it) {
359 result.push_back(*it->second.get());
364 void SavedFilesService::SavedFiles::MaybeCompactSequenceNumbers() {
365 DCHECK_GE(g_max_sequence_number, 0);
366 DCHECK_GE(static_cast<size_t>(g_max_sequence_number),
367 g_max_saved_file_entries);
368 std::map<int, SavedFileEntry*>::reverse_iterator it =
369 saved_file_lru_.rbegin();
370 if (it == saved_file_lru_.rend())
373 // Only compact sequence numbers if the last entry's sequence number is the
374 // maximum value. This should almost never be the case.
375 if (it->first < g_max_sequence_number)
378 int sequence_number = 0;
379 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
380 for (std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin();
381 it != saved_file_lru_.end();
384 if (it->second->sequence_number == sequence_number)
387 SavedFileEntry* file_entry = it->second;
388 file_entry->sequence_number = sequence_number;
389 UpdateSavedFileEntry(prefs, extension_id_, *file_entry);
390 saved_file_lru_.erase(it++);
391 // Provide the following element as an insert hint. While optimized
392 // insertion time with the following element as a hint is only supported by
393 // the spec in C++11, the implementations do support this.
394 it = saved_file_lru_.insert(
395 it, std::make_pair(file_entry->sequence_number, file_entry));
399 void SavedFilesService::SavedFiles::LoadSavedFileEntriesFromPreferences() {
400 ExtensionPrefs* prefs = ExtensionPrefs::Get(context_);
401 std::vector<SavedFileEntry> saved_entries =
402 GetSavedFileEntries(prefs, extension_id_);
403 for (std::vector<SavedFileEntry>::iterator it = saved_entries.begin();
404 it != saved_entries.end();
406 std::unique_ptr<SavedFileEntry> file_entry(new SavedFileEntry(*it));
407 const std::string& id = file_entry->id;
408 saved_file_lru_.insert(
409 std::make_pair(file_entry->sequence_number, file_entry.get()));
410 registered_file_entries_[id] = std::move(file_entry);
415 void SavedFilesService::SetMaxSequenceNumberForTest(int max_value) {
416 g_max_sequence_number = max_value;
420 void SavedFilesService::ClearMaxSequenceNumberForTest() {
421 g_max_sequence_number = kMaxSequenceNumber;
425 void SavedFilesService::SetLruSizeForTest(int size) {
426 g_max_saved_file_entries = size;
430 void SavedFilesService::ClearLruSizeForTest() {
431 g_max_saved_file_entries = kMaxSavedFileEntries;