Add ICU message format support
[chromium-blink-merge.git] / extensions / browser / api / storage / settings_storage_quota_enforcer.cc
blob1fbcbbdc66463d05bdc88a848cb939116d44f107
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"
7 #include "base/bind.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 {
21 namespace {
23 // Resources there are a quota for.
24 enum Resource {
25 QUOTA_BYTES,
26 QUOTA_BYTES_PER_ITEM,
27 MAX_ITEMS
30 // Allocates a setting in a record of total and per-setting usage.
31 void Allocate(
32 const std::string& key,
33 const base::Value& value,
34 size_t* used_total,
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.
50 void Free(
51 size_t* used_total,
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.
62 switch (resource) {
63 case QUOTA_BYTES:
64 name = "QUOTA_BYTES";
65 UMA_HISTOGRAM_COUNTS_100(
66 "Extensions.SettingsQuotaExceeded.TotalBytes", 1);
67 break;
68 case QUOTA_BYTES_PER_ITEM:
69 name = "QUOTA_BYTES_PER_ITEM";
70 UMA_HISTOGRAM_COUNTS_100(
71 "Extensions.SettingsQuotaExceeded.BytesPerSetting", 1);
72 break;
73 case MAX_ITEMS:
74 name = "MAX_ITEMS";
75 UMA_HISTOGRAM_COUNTS_100(
76 "Extensions.SettingsQuotaExceeded.KeyCount", 1);
77 break;
79 CHECK(name);
80 return make_scoped_ptr(new ValueStore::Error(
81 ValueStore::QUOTA_EXCEEDED,
82 base::StringPrintf("%s quota exceeded", name),
83 key.Pass()));
86 } // namespace
88 SettingsStorageQuotaEnforcer::SettingsStorageQuotaEnforcer(
89 const Limits& limits, ValueStore* delegate)
90 : limits_(limits), delegate_(delegate), used_total_(0) {
91 CalculateUsage();
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) {
104 size_t used = 0;
105 for (std::vector<std::string>::const_iterator it = keys.begin();
106 it != keys.end(); ++it) {
107 used += GetBytesInUse(*it);
109 return used;
112 size_t SettingsStorageQuotaEnforcer::GetBytesInUse() {
113 // All ValueStore implementations rely on GetBytesInUse being
114 // implemented here.
115 return used_total_;
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();
166 it.Advance()) {
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();
224 used_total_ = 0;
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();
234 used_total_ = 0u;
235 return false;
237 CalculateUsage();
238 return true;
241 bool SettingsStorageQuotaEnforcer::RestoreKey(const std::string& key) {
242 if (!delegate_->RestoreKey(key))
243 return false;
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);
249 return true;
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();
259 } else {
260 LOG(WARNING) << "Failed to get settings for quota:"
261 << maybe_settings->error().message;
262 return;
266 for (base::DictionaryValue::Iterator it(maybe_settings->settings());
267 !it.IsAtEnd();
268 it.Advance()) {
269 Allocate(it.key(), it.value(), &used_total_, &used_per_setting_);
273 } // namespace extensions