blob: 43a0062196f44a50f5330ed59219936bb3828ec3 [file] [log] [blame]
[email protected]961745f2013-05-25 14:09:241// 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.
4
5#include "apps/saved_files_service.h"
6
7#include <algorithm>
mgiucaf59c7a82015-06-25 09:11:578#include <map>
[email protected]961745f2013-05-25 14:09:249
10#include "apps/saved_files_service_factory.h"
11#include "base/basictypes.h"
mgiucaf59c7a82015-06-25 09:11:5712#include "base/containers/scoped_ptr_hash_map.h"
[email protected]961745f2013-05-25 14:09:2413#include "base/value_conversions.h"
[email protected]fdf40f3e2013-07-11 23:55:4614#include "chrome/browser/chrome_notification_types.h"
[email protected]367d9b172013-12-03 00:31:0215#include "chrome/browser/profiles/profile.h"
[email protected]5a0613d32013-06-17 20:06:5316#include "content/public/browser/notification_service.h"
[email protected]22401dc2014-03-21 01:38:5717#include "extensions/browser/extension_host.h"
[email protected]489db0842014-01-22 18:20:0318#include "extensions/browser/extension_prefs.h"
[email protected]59b0e602014-01-30 00:41:2419#include "extensions/browser/extension_system.h"
[email protected]411f8ae2014-05-22 11:12:2320#include "extensions/browser/extension_util.h"
[email protected]adf5a102014-07-31 12:44:0621#include "extensions/browser/notification_types.h"
[email protected]793964a2013-10-08 00:47:1922#include "extensions/common/permissions/api_permission.h"
[email protected]5a55f3f2013-10-29 01:08:2923#include "extensions/common/permissions/permission_set.h"
[email protected]076ebeda2014-06-06 21:47:2624#include "extensions/common/permissions/permissions_data.h"
[email protected]961745f2013-05-25 14:09:2425
26namespace apps {
27
28using extensions::APIPermission;
29using extensions::Extension;
30using extensions::ExtensionHost;
31using extensions::ExtensionPrefs;
32
33namespace {
34
35// Preference keys
36
37// The file entries that the app has permission to access.
38const char kFileEntries[] = "file_entries";
39
40// The path to a file entry that the app had permission to access.
41const char kFileEntryPath[] = "path";
42
[email protected]6b7ecdd2013-08-29 14:16:2443// Whether or not the the entry refers to a directory.
44const char kFileEntryIsDirectory[] = "is_directory";
45
[email protected]961745f2013-05-25 14:09:2446// The sequence number in the LRU of the file entry.
47const char kFileEntrySequenceNumber[] = "sequence_number";
48
49const size_t kMaxSavedFileEntries = 500;
50const int kMaxSequenceNumber = kint32max;
51
52// These might be different to the constant values in tests.
53size_t g_max_saved_file_entries = kMaxSavedFileEntries;
54int g_max_sequence_number = kMaxSequenceNumber;
55
56// Persists a SavedFileEntry in ExtensionPrefs.
57void AddSavedFileEntry(ExtensionPrefs* prefs,
58 const std::string& extension_id,
59 const SavedFileEntry& file_entry) {
60 ExtensionPrefs::ScopedDictionaryUpdate update(
61 prefs, extension_id, kFileEntries);
[email protected]53b14ea2013-12-21 18:06:3762 base::DictionaryValue* file_entries = update.Get();
[email protected]961745f2013-05-25 14:09:2463 if (!file_entries)
64 file_entries = update.Create();
65 DCHECK(!file_entries->GetDictionaryWithoutPathExpansion(file_entry.id, NULL));
66
[email protected]53b14ea2013-12-21 18:06:3767 base::DictionaryValue* file_entry_dict = new base::DictionaryValue();
[email protected]961745f2013-05-25 14:09:2468 file_entry_dict->Set(kFileEntryPath, CreateFilePathValue(file_entry.path));
[email protected]6b7ecdd2013-08-29 14:16:2469 file_entry_dict->SetBoolean(kFileEntryIsDirectory, file_entry.is_directory);
[email protected]961745f2013-05-25 14:09:2470 file_entry_dict->SetInteger(kFileEntrySequenceNumber,
71 file_entry.sequence_number);
72 file_entries->SetWithoutPathExpansion(file_entry.id, file_entry_dict);
73}
74
75// Updates the sequence_number of a SavedFileEntry persisted in ExtensionPrefs.
76void UpdateSavedFileEntry(ExtensionPrefs* prefs,
77 const std::string& extension_id,
78 const SavedFileEntry& file_entry) {
79 ExtensionPrefs::ScopedDictionaryUpdate update(
80 prefs, extension_id, kFileEntries);
[email protected]53b14ea2013-12-21 18:06:3781 base::DictionaryValue* file_entries = update.Get();
[email protected]961745f2013-05-25 14:09:2482 DCHECK(file_entries);
[email protected]53b14ea2013-12-21 18:06:3783 base::DictionaryValue* file_entry_dict = NULL;
[email protected]961745f2013-05-25 14:09:2484 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);
89}
90
91// Removes a SavedFileEntry from ExtensionPrefs.
92void 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);
[email protected]53b14ea2013-12-21 18:06:3797 base::DictionaryValue* file_entries = update.Get();
[email protected]961745f2013-05-25 14:09:2498 if (!file_entries)
99 file_entries = update.Create();
100 file_entries->RemoveWithoutPathExpansion(file_entry_id, NULL);
101}
102
103// Clears all SavedFileEntry for the app from ExtensionPrefs.
104void ClearSavedFileEntries(ExtensionPrefs* prefs,
105 const std::string& extension_id) {
106 prefs->UpdateExtensionPref(extension_id, kFileEntries, NULL);
107}
108
109// Returns all SavedFileEntries for the app.
110std::vector<SavedFileEntry> GetSavedFileEntries(
111 ExtensionPrefs* prefs,
112 const std::string& extension_id) {
113 std::vector<SavedFileEntry> result;
[email protected]53b14ea2013-12-21 18:06:37114 const base::DictionaryValue* file_entries = NULL;
[email protected]961745f2013-05-25 14:09:24115 if (!prefs->ReadPrefAsDictionary(extension_id, kFileEntries, &file_entries))
116 return result;
117
[email protected]53b14ea2013-12-21 18:06:37118 for (base::DictionaryValue::Iterator it(*file_entries); !it.IsAtEnd();
[email protected]961745f2013-05-25 14:09:24119 it.Advance()) {
[email protected]53b14ea2013-12-21 18:06:37120 const base::DictionaryValue* file_entry = NULL;
[email protected]961745f2013-05-25 14:09:24121 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;
[email protected]6b7ecdd2013-08-29 14:16:24129 bool is_directory = false;
130 file_entry->GetBoolean(kFileEntryIsDirectory, &is_directory);
[email protected]961745f2013-05-25 14:09:24131 int sequence_number = 0;
132 if (!file_entry->GetInteger(kFileEntrySequenceNumber, &sequence_number))
133 continue;
134 if (!sequence_number)
135 continue;
[email protected]6b7ecdd2013-08-29 14:16:24136 result.push_back(
137 SavedFileEntry(it.key(), file_path, is_directory, sequence_number));
[email protected]961745f2013-05-25 14:09:24138 }
139 return result;
140}
141
142} // namespace
143
[email protected]6b7ecdd2013-08-29 14:16:24144SavedFileEntry::SavedFileEntry() : is_directory(false), sequence_number(0) {}
[email protected]961745f2013-05-25 14:09:24145
146SavedFileEntry::SavedFileEntry(const std::string& id,
147 const base::FilePath& path,
[email protected]6b7ecdd2013-08-29 14:16:24148 bool is_directory,
[email protected]961745f2013-05-25 14:09:24149 int sequence_number)
150 : id(id),
151 path(path),
[email protected]6b7ecdd2013-08-29 14:16:24152 is_directory(is_directory),
[email protected]961745f2013-05-25 14:09:24153 sequence_number(sequence_number) {}
154
155class SavedFilesService::SavedFiles {
156 public:
157 SavedFiles(Profile* profile, const std::string& extension_id);
158 ~SavedFiles();
159
160 void RegisterFileEntry(const std::string& id,
[email protected]6b7ecdd2013-08-29 14:16:24161 const base::FilePath& file_path,
162 bool is_directory);
[email protected]961745f2013-05-25 14:09:24163 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;
167
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();
173
174 void LoadSavedFileEntriesFromPreferences();
175
176 Profile* profile_;
177 const std::string extension_id_;
178
179 // Contains all file entries that have been registered, keyed by ID. Owns
180 // values.
mgiucaf59c7a82015-06-25 09:11:57181 base::ScopedPtrHashMap<std::string, scoped_ptr<SavedFileEntry>>
182 registered_file_entries_;
[email protected]961745f2013-05-25 14:09:24183
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_;
188
189 DISALLOW_COPY_AND_ASSIGN(SavedFiles);
190};
191
192// static
193SavedFilesService* SavedFilesService::Get(Profile* profile) {
194 return SavedFilesServiceFactory::GetForProfile(profile);
195}
196
mgiucaf59c7a82015-06-25 09:11:57197SavedFilesService::SavedFilesService(Profile* profile) : profile_(profile) {
[email protected]961745f2013-05-25 14:09:24198 registrar_.Add(this,
[email protected]adf5a102014-07-31 12:44:06199 extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
[email protected]961745f2013-05-25 14:09:24200 content::NotificationService::AllSources());
201 registrar_.Add(this,
202 chrome::NOTIFICATION_APP_TERMINATING,
203 content::NotificationService::AllSources());
204}
205
206SavedFilesService::~SavedFilesService() {}
207
208void SavedFilesService::Observe(int type,
209 const content::NotificationSource& source,
210 const content::NotificationDetails& details) {
211 switch (type) {
[email protected]adf5a102014-07-31 12:44:06212 case extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED: {
[email protected]961745f2013-05-25 14:09:24213 ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
214 const Extension* extension = host->extension();
215 if (extension) {
216 ClearQueueIfNoRetainPermission(extension);
217 Clear(extension->id());
218 }
219 break;
220 }
221
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;
227 }
228 }
229}
230
231void SavedFilesService::RegisterFileEntry(const std::string& extension_id,
232 const std::string& id,
[email protected]6b7ecdd2013-08-29 14:16:24233 const base::FilePath& file_path,
234 bool is_directory) {
235 GetOrInsert(extension_id)->RegisterFileEntry(id, file_path, is_directory);
[email protected]961745f2013-05-25 14:09:24236}
237
238void SavedFilesService::EnqueueFileEntry(const std::string& extension_id,
239 const std::string& id) {
240 GetOrInsert(extension_id)->EnqueueFileEntry(id);
241}
242
243std::vector<SavedFileEntry> SavedFilesService::GetAllFileEntries(
244 const std::string& extension_id) {
[email protected]a2886e8b2013-06-08 05:15:02245 SavedFiles* saved_files = Get(extension_id);
246 if (saved_files)
247 return saved_files->GetAllFileEntries();
248 return GetSavedFileEntries(ExtensionPrefs::Get(profile_), extension_id);
[email protected]961745f2013-05-25 14:09:24249}
250
251bool SavedFilesService::IsRegistered(const std::string& extension_id,
252 const std::string& id) {
253 return GetOrInsert(extension_id)->IsRegistered(id);
254}
255
256const SavedFileEntry* SavedFilesService::GetFileEntry(
257 const std::string& extension_id,
258 const std::string& id) {
259 return GetOrInsert(extension_id)->GetFileEntry(id);
260}
261
262void SavedFilesService::ClearQueueIfNoRetainPermission(
263 const Extension* extension) {
[email protected]411f8ae2014-05-22 11:12:23264 if (extensions::util::IsEphemeralApp(extension->id(), profile_) ||
rdevlin.cronind630c302015-09-30 20:19:33265 !extension->permissions_data()->active_permissions().HasAPIPermission(
[email protected]73f73a02013-07-04 02:15:12266 APIPermission::kFileSystemRetainEntries)) {
[email protected]a2886e8b2013-06-08 05:15:02267 ClearQueue(extension);
[email protected]961745f2013-05-25 14:09:24268 }
269}
270
[email protected]a2886e8b2013-06-08 05:15:02271void SavedFilesService::ClearQueue(const extensions::Extension* extension) {
272 ClearSavedFileEntries(ExtensionPrefs::Get(profile_), extension->id());
273 Clear(extension->id());
274}
275
276SavedFilesService::SavedFiles* SavedFilesService::Get(
277 const std::string& extension_id) const {
mgiucaf59c7a82015-06-25 09:11:57278 base::ScopedPtrMap<std::string, scoped_ptr<SavedFiles>>::const_iterator it =
[email protected]961745f2013-05-25 14:09:24279 extension_id_to_saved_files_.find(extension_id);
280 if (it != extension_id_to_saved_files_.end())
281 return it->second;
282
[email protected]a2886e8b2013-06-08 05:15:02283 return NULL;
284}
285
286SavedFilesService::SavedFiles* SavedFilesService::GetOrInsert(
287 const std::string& extension_id) {
288 SavedFiles* saved_files = Get(extension_id);
289 if (saved_files)
290 return saved_files;
291
mgiucaf59c7a82015-06-25 09:11:57292 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());
[email protected]961745f2013-05-25 14:09:24296 return saved_files;
297}
298
299void SavedFilesService::Clear(const std::string& extension_id) {
mgiucaf59c7a82015-06-25 09:11:57300 extension_id_to_saved_files_.erase(extension_id);
[email protected]961745f2013-05-25 14:09:24301}
302
303SavedFilesService::SavedFiles::SavedFiles(Profile* profile,
304 const std::string& extension_id)
mgiucaf59c7a82015-06-25 09:11:57305 : profile_(profile), extension_id_(extension_id) {
[email protected]961745f2013-05-25 14:09:24306 LoadSavedFileEntriesFromPreferences();
307}
308
309SavedFilesService::SavedFiles::~SavedFiles() {}
310
311void SavedFilesService::SavedFiles::RegisterFileEntry(
312 const std::string& id,
[email protected]6b7ecdd2013-08-29 14:16:24313 const base::FilePath& file_path,
314 bool is_directory) {
[email protected]961745f2013-05-25 14:09:24315 if (ContainsKey(registered_file_entries_, id))
316 return;
317
mgiucaf59c7a82015-06-25 09:11:57318 registered_file_entries_.add(
319 id, make_scoped_ptr(new SavedFileEntry(id, file_path, is_directory, 0)));
[email protected]961745f2013-05-25 14:09:24320}
321
322void SavedFilesService::SavedFiles::EnqueueFileEntry(const std::string& id) {
mgiucaf59c7a82015-06-25 09:11:57323 auto it = registered_file_entries_.find(id);
[email protected]961745f2013-05-25 14:09:24324 DCHECK(it != registered_file_entries_.end());
325
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;
334
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;
339 }
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);
353 }
354 }
355 MaybeCompactSequenceNumbers();
356}
357
358bool SavedFilesService::SavedFiles::IsRegistered(const std::string& id) const {
359 return ContainsKey(registered_file_entries_, id);
360}
361
362const SavedFileEntry* SavedFilesService::SavedFiles::GetFileEntry(
363 const std::string& id) const {
mgiucaf59c7a82015-06-25 09:11:57364 auto it = registered_file_entries_.find(id);
[email protected]961745f2013-05-25 14:09:24365 if (it == registered_file_entries_.end())
366 return NULL;
367
368 return it->second;
369}
370
371std::vector<SavedFileEntry> SavedFilesService::SavedFiles::GetAllFileEntries()
372 const {
373 std::vector<SavedFileEntry> result;
mgiucaf59c7a82015-06-25 09:11:57374 for (auto it = registered_file_entries_.begin();
375 it != registered_file_entries_.end(); ++it) {
[email protected]961745f2013-05-25 14:09:24376 result.push_back(*it->second);
377 }
378 return result;
379}
380
381void 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;
389
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;
394
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;
403
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));
413 }
414}
415
416void 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) {
mgiucaf59c7a82015-06-25 09:11:57423 scoped_ptr<SavedFileEntry> file_entry(new SavedFileEntry(*it));
424 const std::string& id = file_entry->id;
[email protected]961745f2013-05-25 14:09:24425 saved_file_lru_.insert(
mgiucaf59c7a82015-06-25 09:11:57426 std::make_pair(file_entry->sequence_number, file_entry.get()));
427 registered_file_entries_.add(id, file_entry.Pass());
[email protected]961745f2013-05-25 14:09:24428 }
429}
430
431// static
432void SavedFilesService::SetMaxSequenceNumberForTest(int max_value) {
433 g_max_sequence_number = max_value;
434}
435
436// static
437void SavedFilesService::ClearMaxSequenceNumberForTest() {
438 g_max_sequence_number = kMaxSequenceNumber;
439}
440
441// static
442void SavedFilesService::SetLruSizeForTest(int size) {
443 g_max_saved_file_entries = size;
444}
445
446// static
447void SavedFilesService::ClearLruSizeForTest() {
448 g_max_saved_file_entries = kMaxSavedFileEntries;
449}
450
451} // namespace apps