Add ICU message format support
[chromium-blink-merge.git] / apps / saved_files_service.cc
blob49e0b56fb86e0531ab02b795773bfb7aad7d08fd
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"
7 #include <algorithm>
8 #include <map>
10 #include "apps/saved_files_service_factory.h"
11 #include "base/basictypes.h"
12 #include "base/containers/scoped_ptr_hash_map.h"
13 #include "base/value_conversions.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "content/public/browser/notification_service.h"
17 #include "extensions/browser/extension_host.h"
18 #include "extensions/browser/extension_prefs.h"
19 #include "extensions/browser/extension_system.h"
20 #include "extensions/browser/extension_util.h"
21 #include "extensions/browser/notification_types.h"
22 #include "extensions/common/permissions/api_permission.h"
23 #include "extensions/common/permissions/permission_set.h"
24 #include "extensions/common/permissions/permissions_data.h"
26 namespace apps {
28 using extensions::APIPermission;
29 using extensions::Extension;
30 using extensions::ExtensionHost;
31 using extensions::ExtensionPrefs;
33 namespace {
35 // Preference keys
37 // The file entries that the app has permission to access.
38 const char kFileEntries[] = "file_entries";
40 // The path to a file entry that the app had permission to access.
41 const char kFileEntryPath[] = "path";
43 // Whether or not the the entry refers to a directory.
44 const char kFileEntryIsDirectory[] = "is_directory";
46 // The sequence number in the LRU of the file entry.
47 const char kFileEntrySequenceNumber[] = "sequence_number";
49 const size_t kMaxSavedFileEntries = 500;
50 const int kMaxSequenceNumber = kint32max;
52 // These might be different to the constant values in tests.
53 size_t g_max_saved_file_entries = kMaxSavedFileEntries;
54 int g_max_sequence_number = kMaxSequenceNumber;
56 // Persists a SavedFileEntry in ExtensionPrefs.
57 void AddSavedFileEntry(ExtensionPrefs* prefs,
58 const std::string& extension_id,
59 const SavedFileEntry& file_entry) {
60 ExtensionPrefs::ScopedDictionaryUpdate update(
61 prefs, extension_id, kFileEntries);
62 base::DictionaryValue* file_entries = update.Get();
63 if (!file_entries)
64 file_entries = update.Create();
65 DCHECK(!file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, NULL));
67 base::DictionaryValue* file_entry_dict = new 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, file_entry_dict);
75 // Updates the sequence_number of a SavedFileEntry persisted in ExtensionPrefs.
76 void UpdateSavedFileEntry(ExtensionPrefs* prefs,
77 const std::string& extension_id,
78 const SavedFileEntry& file_entry) {
79 ExtensionPrefs::ScopedDictionaryUpdate update(
80 prefs, extension_id, kFileEntries);
81 base::DictionaryValue* file_entries = update.Get();
82 DCHECK(file_entries);
83 base::DictionaryValue* file_entry_dict = NULL;
84 file_entries->GetDictionaryWithoutPathExpansion(file_entry.id,
85 &file_entry_dict);
86 DCHECK(file_entry_dict);
87 file_entry_dict->SetInteger(kFileEntrySequenceNumber,
88 file_entry.sequence_number);
91 // Removes a SavedFileEntry from ExtensionPrefs.
92 void RemoveSavedFileEntry(ExtensionPrefs* prefs,
93 const std::string& extension_id,
94 const std::string& file_entry_id) {
95 ExtensionPrefs::ScopedDictionaryUpdate update(
96 prefs, extension_id, kFileEntries);
97 base::DictionaryValue* file_entries = update.Get();
98 if (!file_entries)
99 file_entries = update.Create();
100 file_entries->RemoveWithoutPathExpansion(file_entry_id, NULL);
103 // Clears all SavedFileEntry for the app from ExtensionPrefs.
104 void ClearSavedFileEntries(ExtensionPrefs* prefs,
105 const std::string& extension_id) {
106 prefs->UpdateExtensionPref(extension_id, kFileEntries, NULL);
109 // Returns all SavedFileEntries for the app.
110 std::vector<SavedFileEntry> GetSavedFileEntries(
111 ExtensionPrefs* prefs,
112 const std::string& extension_id) {
113 std::vector<SavedFileEntry> result;
114 const base::DictionaryValue* file_entries = NULL;
115 if (!prefs->ReadPrefAsDictionary(extension_id, kFileEntries, &file_entries))
116 return result;
118 for (base::DictionaryValue::Iterator it(*file_entries); !it.IsAtEnd();
119 it.Advance()) {
120 const base::DictionaryValue* file_entry = NULL;
121 if (!it.value().GetAsDictionary(&file_entry))
122 continue;
123 const base::Value* path_value;
124 if (!file_entry->Get(kFileEntryPath, &path_value))
125 continue;
126 base::FilePath file_path;
127 if (!GetValueAsFilePath(*path_value, &file_path))
128 continue;
129 bool is_directory = false;
130 file_entry->GetBoolean(kFileEntryIsDirectory, &is_directory);
131 int sequence_number = 0;
132 if (!file_entry->GetInteger(kFileEntrySequenceNumber, &sequence_number))
133 continue;
134 if (!sequence_number)
135 continue;
136 result.push_back(
137 SavedFileEntry(it.key(), file_path, is_directory, sequence_number));
139 return result;
142 } // namespace
144 SavedFileEntry::SavedFileEntry() : is_directory(false), sequence_number(0) {}
146 SavedFileEntry::SavedFileEntry(const std::string& id,
147 const base::FilePath& path,
148 bool is_directory,
149 int sequence_number)
150 : id(id),
151 path(path),
152 is_directory(is_directory),
153 sequence_number(sequence_number) {}
155 class SavedFilesService::SavedFiles {
156 public:
157 SavedFiles(Profile* profile, const std::string& extension_id);
158 ~SavedFiles();
160 void RegisterFileEntry(const std::string& id,
161 const base::FilePath& file_path,
162 bool is_directory);
163 void EnqueueFileEntry(const std::string& id);
164 bool IsRegistered(const std::string& id) const;
165 const SavedFileEntry* GetFileEntry(const std::string& id) const;
166 std::vector<SavedFileEntry> GetAllFileEntries() const;
168 private:
169 // Compacts sequence numbers if the largest sequence number is
170 // g_max_sequence_number. Outside of testing, it is set to kint32max, so this
171 // will almost never do any real work.
172 void MaybeCompactSequenceNumbers();
174 void LoadSavedFileEntriesFromPreferences();
176 Profile* profile_;
177 const std::string extension_id_;
179 // Contains all file entries that have been registered, keyed by ID. Owns
180 // values.
181 base::ScopedPtrHashMap<std::string, scoped_ptr<SavedFileEntry>>
182 registered_file_entries_;
184 // The queue of file entries that have been retained, keyed by
185 // sequence_number. Values are a subset of values in registered_file_entries_.
186 // This should be kept in sync with file entries stored in extension prefs.
187 std::map<int, SavedFileEntry*> saved_file_lru_;
189 DISALLOW_COPY_AND_ASSIGN(SavedFiles);
192 // static
193 SavedFilesService* SavedFilesService::Get(Profile* profile) {
194 return SavedFilesServiceFactory::GetForProfile(profile);
197 SavedFilesService::SavedFilesService(Profile* profile) : profile_(profile) {
198 registrar_.Add(this,
199 extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
200 content::NotificationService::AllSources());
201 registrar_.Add(this,
202 chrome::NOTIFICATION_APP_TERMINATING,
203 content::NotificationService::AllSources());
206 SavedFilesService::~SavedFilesService() {}
208 void SavedFilesService::Observe(int type,
209 const content::NotificationSource& source,
210 const content::NotificationDetails& details) {
211 switch (type) {
212 case extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
213 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
214 const Extension* extension = host->extension();
215 if (extension) {
216 ClearQueueIfNoRetainPermission(extension);
217 Clear(extension->id());
219 break;
222 case chrome::NOTIFICATION_APP_TERMINATING: {
223 // Stop listening to NOTIFICATION_EXTENSION_HOST_DESTROYED in particular
224 // as all extension hosts will be destroyed as a result of shutdown.
225 registrar_.RemoveAll();
226 break;
231 void SavedFilesService::RegisterFileEntry(const std::string& extension_id,
232 const std::string& id,
233 const base::FilePath& file_path,
234 bool is_directory) {
235 GetOrInsert(extension_id)->RegisterFileEntry(id, file_path, is_directory);
238 void SavedFilesService::EnqueueFileEntry(const std::string& extension_id,
239 const std::string& id) {
240 GetOrInsert(extension_id)->EnqueueFileEntry(id);
243 std::vector<SavedFileEntry> SavedFilesService::GetAllFileEntries(
244 const std::string& extension_id) {
245 SavedFiles* saved_files = Get(extension_id);
246 if (saved_files)
247 return saved_files->GetAllFileEntries();
248 return GetSavedFileEntries(ExtensionPrefs::Get(profile_), extension_id);
251 bool SavedFilesService::IsRegistered(const std::string& extension_id,
252 const std::string& id) {
253 return GetOrInsert(extension_id)->IsRegistered(id);
256 const SavedFileEntry* SavedFilesService::GetFileEntry(
257 const std::string& extension_id,
258 const std::string& id) {
259 return GetOrInsert(extension_id)->GetFileEntry(id);
262 void SavedFilesService::ClearQueueIfNoRetainPermission(
263 const Extension* extension) {
264 if (extensions::util::IsEphemeralApp(extension->id(), profile_) ||
265 !extension->permissions_data()->active_permissions()->HasAPIPermission(
266 APIPermission::kFileSystemRetainEntries)) {
267 ClearQueue(extension);
271 void SavedFilesService::ClearQueue(const extensions::Extension* extension) {
272 ClearSavedFileEntries(ExtensionPrefs::Get(profile_), extension->id());
273 Clear(extension->id());
276 SavedFilesService::SavedFiles* SavedFilesService::Get(
277 const std::string& extension_id) const {
278 base::ScopedPtrMap<std::string, scoped_ptr<SavedFiles>>::const_iterator it =
279 extension_id_to_saved_files_.find(extension_id);
280 if (it != extension_id_to_saved_files_.end())
281 return it->second;
283 return NULL;
286 SavedFilesService::SavedFiles* SavedFilesService::GetOrInsert(
287 const std::string& extension_id) {
288 SavedFiles* saved_files = Get(extension_id);
289 if (saved_files)
290 return saved_files;
292 scoped_ptr<SavedFiles> scoped_saved_files(
293 new SavedFiles(profile_, extension_id));
294 saved_files = scoped_saved_files.get();
295 extension_id_to_saved_files_.insert(extension_id, scoped_saved_files.Pass());
296 return saved_files;
299 void SavedFilesService::Clear(const std::string& extension_id) {
300 extension_id_to_saved_files_.erase(extension_id);
303 SavedFilesService::SavedFiles::SavedFiles(Profile* profile,
304 const std::string& extension_id)
305 : profile_(profile), extension_id_(extension_id) {
306 LoadSavedFileEntriesFromPreferences();
309 SavedFilesService::SavedFiles::~SavedFiles() {}
311 void SavedFilesService::SavedFiles::RegisterFileEntry(
312 const std::string& id,
313 const base::FilePath& file_path,
314 bool is_directory) {
315 if (ContainsKey(registered_file_entries_, id))
316 return;
318 registered_file_entries_.add(
319 id, make_scoped_ptr(new SavedFileEntry(id, file_path, is_directory, 0)));
322 void SavedFilesService::SavedFiles::EnqueueFileEntry(const std::string& id) {
323 auto it = registered_file_entries_.find(id);
324 DCHECK(it != registered_file_entries_.end());
326 SavedFileEntry* file_entry = it->second;
327 int old_sequence_number = file_entry->sequence_number;
328 if (!saved_file_lru_.empty()) {
329 // Get the sequence number after the last file entry in the LRU.
330 std::map<int, SavedFileEntry*>::reverse_iterator it =
331 saved_file_lru_.rbegin();
332 if (it->second == file_entry)
333 return;
335 file_entry->sequence_number = it->first + 1;
336 } else {
337 // The first sequence number is 1, as 0 means the entry is not in the LRU.
338 file_entry->sequence_number = 1;
340 saved_file_lru_.insert(
341 std::make_pair(file_entry->sequence_number, file_entry));
342 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
343 if (old_sequence_number) {
344 saved_file_lru_.erase(old_sequence_number);
345 UpdateSavedFileEntry(prefs, extension_id_, *file_entry);
346 } else {
347 AddSavedFileEntry(prefs, extension_id_, *file_entry);
348 if (saved_file_lru_.size() > g_max_saved_file_entries) {
349 std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin();
350 it->second->sequence_number = 0;
351 RemoveSavedFileEntry(prefs, extension_id_, it->second->id);
352 saved_file_lru_.erase(it);
355 MaybeCompactSequenceNumbers();
358 bool SavedFilesService::SavedFiles::IsRegistered(const std::string& id) const {
359 return ContainsKey(registered_file_entries_, id);
362 const SavedFileEntry* SavedFilesService::SavedFiles::GetFileEntry(
363 const std::string& id) const {
364 auto it = registered_file_entries_.find(id);
365 if (it == registered_file_entries_.end())
366 return NULL;
368 return it->second;
371 std::vector<SavedFileEntry> SavedFilesService::SavedFiles::GetAllFileEntries()
372 const {
373 std::vector<SavedFileEntry> result;
374 for (auto it = registered_file_entries_.begin();
375 it != registered_file_entries_.end(); ++it) {
376 result.push_back(*it->second);
378 return result;
381 void SavedFilesService::SavedFiles::MaybeCompactSequenceNumbers() {
382 DCHECK_GE(g_max_sequence_number, 0);
383 DCHECK_GE(static_cast<size_t>(g_max_sequence_number),
384 g_max_saved_file_entries);
385 std::map<int, SavedFileEntry*>::reverse_iterator it =
386 saved_file_lru_.rbegin();
387 if (it == saved_file_lru_.rend())
388 return;
390 // Only compact sequence numbers if the last entry's sequence number is the
391 // maximum value. This should almost never be the case.
392 if (it->first < g_max_sequence_number)
393 return;
395 int sequence_number = 0;
396 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
397 for (std::map<int, SavedFileEntry*>::iterator it = saved_file_lru_.begin();
398 it != saved_file_lru_.end();
399 ++it) {
400 sequence_number++;
401 if (it->second->sequence_number == sequence_number)
402 continue;
404 SavedFileEntry* file_entry = it->second;
405 file_entry->sequence_number = sequence_number;
406 UpdateSavedFileEntry(prefs, extension_id_, *file_entry);
407 saved_file_lru_.erase(it++);
408 // Provide the following element as an insert hint. While optimized
409 // insertion time with the following element as a hint is only supported by
410 // the spec in C++11, the implementations do support this.
411 it = saved_file_lru_.insert(
412 it, std::make_pair(file_entry->sequence_number, file_entry));
416 void SavedFilesService::SavedFiles::LoadSavedFileEntriesFromPreferences() {
417 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_);
418 std::vector<SavedFileEntry> saved_entries =
419 GetSavedFileEntries(prefs, extension_id_);
420 for (std::vector<SavedFileEntry>::iterator it = saved_entries.begin();
421 it != saved_entries.end();
422 ++it) {
423 scoped_ptr<SavedFileEntry> file_entry(new SavedFileEntry(*it));
424 const std::string& id = file_entry->id;
425 saved_file_lru_.insert(
426 std::make_pair(file_entry->sequence_number, file_entry.get()));
427 registered_file_entries_.add(id, file_entry.Pass());
431 // static
432 void SavedFilesService::SetMaxSequenceNumberForTest(int max_value) {
433 g_max_sequence_number = max_value;
436 // static
437 void SavedFilesService::ClearMaxSequenceNumberForTest() {
438 g_max_sequence_number = kMaxSequenceNumber;
441 // static
442 void SavedFilesService::SetLruSizeForTest(int size) {
443 g_max_saved_file_entries = size;
446 // static
447 void SavedFilesService::ClearLruSizeForTest() {
448 g_max_saved_file_entries = kMaxSavedFileEntries;
451 } // namespace apps