1 // Copyright 2014 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 "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
8 #include "base/json/json_writer.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/metrics/histogram.h"
12 #include "base/stl_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "extensions/browser/value_store/value_store_util.h"
15 #include "extensions/common/extension_api.h"
17 namespace util
= value_store_util
;
19 namespace extensions
{
23 // Resources there are a quota for.
30 // Allocates a setting in a record of total and per-setting usage.
32 const std::string
& key
,
33 const base::Value
& value
,
35 std::map
<std::string
, size_t>* used_per_setting
) {
36 // Calculate the setting size based on its JSON serialization size.
37 // TODO(kalman): Does this work with different encodings?
38 // TODO(kalman): This is duplicating work that the leveldb delegate
39 // implementation is about to do, and it would be nice to avoid this.
40 std::string value_as_json
;
41 base::JSONWriter::Write(value
, &value_as_json
);
42 size_t new_size
= key
.size() + value_as_json
.size();
43 size_t existing_size
= (*used_per_setting
)[key
];
45 *used_total
+= (new_size
- existing_size
);
46 (*used_per_setting
)[key
] = new_size
;
49 // Frees the allocation of a setting in a record of total and per-setting usage.
52 std::map
<std::string
, size_t>* used_per_setting
,
53 const std::string
& key
) {
54 *used_total
-= (*used_per_setting
)[key
];
55 used_per_setting
->erase(key
);
58 scoped_ptr
<ValueStore::Error
> QuotaExceededError(Resource resource
,
59 scoped_ptr
<std::string
> key
) {
60 const char* name
= NULL
;
61 // TODO(kalman): These hisograms are both silly and untracked. Fix.
65 UMA_HISTOGRAM_COUNTS_100(
66 "Extensions.SettingsQuotaExceeded.TotalBytes", 1);
68 case QUOTA_BYTES_PER_ITEM
:
69 name
= "QUOTA_BYTES_PER_ITEM";
70 UMA_HISTOGRAM_COUNTS_100(
71 "Extensions.SettingsQuotaExceeded.BytesPerSetting", 1);
75 UMA_HISTOGRAM_COUNTS_100(
76 "Extensions.SettingsQuotaExceeded.KeyCount", 1);
80 return make_scoped_ptr(new ValueStore::Error(
81 ValueStore::QUOTA_EXCEEDED
,
82 base::StringPrintf("%s quota exceeded", name
),
88 SettingsStorageQuotaEnforcer::SettingsStorageQuotaEnforcer(
89 const Limits
& limits
, ValueStore
* delegate
)
90 : limits_(limits
), delegate_(delegate
), used_total_(0) {
94 SettingsStorageQuotaEnforcer::~SettingsStorageQuotaEnforcer() {}
96 size_t SettingsStorageQuotaEnforcer::GetBytesInUse(const std::string
& key
) {
97 std::map
<std::string
, size_t>::iterator maybe_used
=
98 used_per_setting_
.find(key
);
99 return maybe_used
== used_per_setting_
.end() ? 0u : maybe_used
->second
;
102 size_t SettingsStorageQuotaEnforcer::GetBytesInUse(
103 const std::vector
<std::string
>& keys
) {
105 for (std::vector
<std::string
>::const_iterator it
= keys
.begin();
106 it
!= keys
.end(); ++it
) {
107 used
+= GetBytesInUse(*it
);
112 size_t SettingsStorageQuotaEnforcer::GetBytesInUse() {
113 // All ValueStore implementations rely on GetBytesInUse being
118 ValueStore::ReadResult
SettingsStorageQuotaEnforcer::Get(
119 const std::string
& key
) {
120 return delegate_
->Get(key
);
123 ValueStore::ReadResult
SettingsStorageQuotaEnforcer::Get(
124 const std::vector
<std::string
>& keys
) {
125 return delegate_
->Get(keys
);
128 ValueStore::ReadResult
SettingsStorageQuotaEnforcer::Get() {
129 return delegate_
->Get();
132 ValueStore::WriteResult
SettingsStorageQuotaEnforcer::Set(
133 WriteOptions options
, const std::string
& key
, const base::Value
& value
) {
134 size_t new_used_total
= used_total_
;
135 std::map
<std::string
, size_t> new_used_per_setting
= used_per_setting_
;
136 Allocate(key
, value
, &new_used_total
, &new_used_per_setting
);
138 if (!(options
& IGNORE_QUOTA
)) {
139 if (new_used_total
> limits_
.quota_bytes
) {
140 return MakeWriteResult(
141 QuotaExceededError(QUOTA_BYTES
, util::NewKey(key
)));
143 if (new_used_per_setting
[key
] > limits_
.quota_bytes_per_item
) {
144 return MakeWriteResult(
145 QuotaExceededError(QUOTA_BYTES_PER_ITEM
, util::NewKey(key
)));
147 if (new_used_per_setting
.size() > limits_
.max_items
)
148 return MakeWriteResult(QuotaExceededError(MAX_ITEMS
, util::NewKey(key
)));
151 WriteResult result
= delegate_
->Set(options
, key
, value
);
152 if (result
->HasError()) {
153 return result
.Pass();
156 used_total_
= new_used_total
;
157 used_per_setting_
.swap(new_used_per_setting
);
158 return result
.Pass();
161 ValueStore::WriteResult
SettingsStorageQuotaEnforcer::Set(
162 WriteOptions options
, const base::DictionaryValue
& values
) {
163 size_t new_used_total
= used_total_
;
164 std::map
<std::string
, size_t> new_used_per_setting
= used_per_setting_
;
165 for (base::DictionaryValue::Iterator
it(values
); !it
.IsAtEnd();
167 Allocate(it
.key(), it
.value(), &new_used_total
, &new_used_per_setting
);
169 if (!(options
& IGNORE_QUOTA
) &&
170 new_used_per_setting
[it
.key()] > limits_
.quota_bytes_per_item
) {
171 return MakeWriteResult(
172 QuotaExceededError(QUOTA_BYTES_PER_ITEM
, util::NewKey(it
.key())));
176 if (!(options
& IGNORE_QUOTA
)) {
177 if (new_used_total
> limits_
.quota_bytes
)
178 return MakeWriteResult(QuotaExceededError(QUOTA_BYTES
, util::NoKey()));
179 if (new_used_per_setting
.size() > limits_
.max_items
)
180 return MakeWriteResult(QuotaExceededError(MAX_ITEMS
, util::NoKey()));
183 WriteResult result
= delegate_
->Set(options
, values
);
184 if (result
->HasError()) {
185 return result
.Pass();
188 used_total_
= new_used_total
;
189 used_per_setting_
= new_used_per_setting
;
190 return result
.Pass();
193 ValueStore::WriteResult
SettingsStorageQuotaEnforcer::Remove(
194 const std::string
& key
) {
195 WriteResult result
= delegate_
->Remove(key
);
196 if (result
->HasError()) {
197 return result
.Pass();
199 Free(&used_total_
, &used_per_setting_
, key
);
200 return result
.Pass();
203 ValueStore::WriteResult
SettingsStorageQuotaEnforcer::Remove(
204 const std::vector
<std::string
>& keys
) {
205 WriteResult result
= delegate_
->Remove(keys
);
206 if (result
->HasError()) {
207 return result
.Pass();
210 for (std::vector
<std::string
>::const_iterator it
= keys
.begin();
211 it
!= keys
.end(); ++it
) {
212 Free(&used_total_
, &used_per_setting_
, *it
);
214 return result
.Pass();
217 ValueStore::WriteResult
SettingsStorageQuotaEnforcer::Clear() {
218 WriteResult result
= delegate_
->Clear();
219 if (result
->HasError()) {
220 return result
.Pass();
223 used_per_setting_
.clear();
225 return result
.Pass();
228 bool SettingsStorageQuotaEnforcer::Restore() {
229 if (!delegate_
->Restore()) {
230 // If we failed, we can't calculate the usage - that's okay, though, because
231 // next time we Restore() (if it succeeds) we will recalculate usage anyway.
232 // So reset storage counters now to free up resources.
233 used_per_setting_
.clear();
241 bool SettingsStorageQuotaEnforcer::RestoreKey(const std::string
& key
) {
242 if (!delegate_
->RestoreKey(key
))
245 ReadResult result
= Get(key
);
246 // If the key was deleted as a result of the Restore() call, free it.
247 if (!result
->settings().HasKey(key
) && ContainsKey(used_per_setting_
, key
))
248 Free(&used_total_
, &used_per_setting_
, key
);
252 void SettingsStorageQuotaEnforcer::CalculateUsage() {
253 ReadResult maybe_settings
= delegate_
->Get();
254 if (maybe_settings
->HasError()) {
255 // Try to restore the database if it's corrupt.
256 if (maybe_settings
->error().code
== ValueStore::CORRUPTION
&&
257 delegate_
->Restore()) {
258 maybe_settings
= delegate_
->Get();
260 LOG(WARNING
) << "Failed to get settings for quota:"
261 << maybe_settings
->error().message
;
266 for (base::DictionaryValue::Iterator
it(maybe_settings
->settings());
269 Allocate(it
.key(), it
.value(), &used_total_
, &used_per_setting_
);
273 } // namespace extensions