blob: 87c6bc64deaf6a38d7a5c81f5c459c1e12a42aed [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
#include <memory>
#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "extensions/common/extension_api.h"
namespace extensions {
namespace {
// Resources there are a quota for.
enum Resource {
QUOTA_BYTES,
QUOTA_BYTES_PER_ITEM,
MAX_ITEMS
};
// Allocates a setting in a record of total and per-setting usage.
void Allocate(
const std::string& key,
const base::Value& value,
size_t* used_total,
std::map<std::string, size_t>* used_per_setting) {
// Calculate the setting size based on its JSON serialization size.
// TODO(kalman): Does this work with different encodings?
// TODO(kalman): This is duplicating work that the leveldb delegate
// implementation is about to do, and it would be nice to avoid this.
std::string value_as_json;
base::JSONWriter::Write(value, &value_as_json);
size_t new_size = key.size() + value_as_json.size();
size_t existing_size = (*used_per_setting)[key];
*used_total += (new_size - existing_size);
(*used_per_setting)[key] = new_size;
}
ValueStore::Status QuotaExceededError(Resource resource) {
const char* name = NULL;
switch (resource) {
case QUOTA_BYTES:
name = "QUOTA_BYTES";
break;
case QUOTA_BYTES_PER_ITEM:
name = "QUOTA_BYTES_PER_ITEM";
break;
case MAX_ITEMS:
name = "MAX_ITEMS";
break;
}
CHECK(name);
return ValueStore::Status(ValueStore::QUOTA_EXCEEDED,
base::StringPrintf("%s quota exceeded", name));
}
} // namespace
SettingsStorageQuotaEnforcer::SettingsStorageQuotaEnforcer(
const Limits& limits,
std::unique_ptr<ValueStore> delegate)
: limits_(limits),
delegate_(std::move(delegate)),
used_total_(0),
usage_calculated_(false) {}
SettingsStorageQuotaEnforcer::~SettingsStorageQuotaEnforcer() {}
size_t SettingsStorageQuotaEnforcer::GetBytesInUse(const std::string& key) {
LazyCalculateUsage();
auto maybe_used = used_per_setting_.find(key);
return maybe_used == used_per_setting_.end() ? 0u : maybe_used->second;
}
size_t SettingsStorageQuotaEnforcer::GetBytesInUse(
const std::vector<std::string>& keys) {
size_t used = 0;
for (const std::string& key : keys)
used += GetBytesInUse(key);
return used;
}
size_t SettingsStorageQuotaEnforcer::GetBytesInUse() {
// All ValueStore implementations rely on GetBytesInUse being
// implemented here.
LazyCalculateUsage();
return used_total_;
}
ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
const std::string& key) {
return HandleResult(delegate_->Get(key));
}
ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
const std::vector<std::string>& keys) {
return HandleResult(delegate_->Get(keys));
}
ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get() {
return HandleResult(delegate_->Get());
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set(
WriteOptions options, const std::string& key, const base::Value& value) {
LazyCalculateUsage();
size_t new_used_total = used_total_;
std::map<std::string, size_t> new_used_per_setting = used_per_setting_;
Allocate(key, value, &new_used_total, &new_used_per_setting);
if (!(options & IGNORE_QUOTA)) {
if (new_used_total > limits_.quota_bytes)
return WriteResult(QuotaExceededError(QUOTA_BYTES));
if (new_used_per_setting[key] > limits_.quota_bytes_per_item)
return WriteResult(QuotaExceededError(QUOTA_BYTES_PER_ITEM));
if (new_used_per_setting.size() > limits_.max_items)
return WriteResult(QuotaExceededError(MAX_ITEMS));
}
WriteResult result = HandleResult(delegate_->Set(options, key, value));
if (!result.status().ok())
return result;
if (usage_calculated_) {
used_total_ = new_used_total;
used_per_setting_.swap(new_used_per_setting);
}
return result;
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set(
WriteOptions options, const base::DictionaryValue& values) {
LazyCalculateUsage();
size_t new_used_total = used_total_;
std::map<std::string, size_t> new_used_per_setting = used_per_setting_;
for (base::DictionaryValue::Iterator it(values); !it.IsAtEnd();
it.Advance()) {
Allocate(it.key(), it.value(), &new_used_total, &new_used_per_setting);
if (!(options & IGNORE_QUOTA) &&
new_used_per_setting[it.key()] > limits_.quota_bytes_per_item) {
return WriteResult(QuotaExceededError(QUOTA_BYTES_PER_ITEM));
}
}
if (!(options & IGNORE_QUOTA)) {
if (new_used_total > limits_.quota_bytes)
return WriteResult(QuotaExceededError(QUOTA_BYTES));
if (new_used_per_setting.size() > limits_.max_items)
return WriteResult(QuotaExceededError(MAX_ITEMS));
}
WriteResult result = HandleResult(delegate_->Set(options, values));
if (!result.status().ok())
return result;
if (usage_calculated_) {
used_total_ = new_used_total;
used_per_setting_ = new_used_per_setting;
}
return result;
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove(
const std::string& key) {
LazyCalculateUsage();
WriteResult result = HandleResult(delegate_->Remove(key));
if (!result.status().ok())
return result;
Free(key);
return result;
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove(
const std::vector<std::string>& keys) {
WriteResult result = HandleResult(delegate_->Remove(keys));
if (!result.status().ok())
return result;
for (const std::string& key : keys)
Free(key);
return result;
}
ValueStore::WriteResult SettingsStorageQuotaEnforcer::Clear() {
LazyCalculateUsage();
WriteResult result = HandleResult(delegate_->Clear());
if (!result.status().ok())
return result;
used_per_setting_.clear();
used_total_ = 0u;
return result;
}
template <class T>
T SettingsStorageQuotaEnforcer::HandleResult(T result) {
if (result.status().restore_status != RESTORE_NONE) {
// Restoration means that an unknown amount, possibly all, of the data was
// lost from the database. Reset our counters - they will be lazily
// recalculated if/when needed.
used_per_setting_.clear();
used_total_ = 0u;
usage_calculated_ = false;
}
return result;
}
void SettingsStorageQuotaEnforcer::LazyCalculateUsage() {
if (usage_calculated_)
return;
DCHECK_EQ(0u, used_total_);
DCHECK(used_per_setting_.empty());
ReadResult maybe_settings = HandleResult(delegate_->Get());
if (!maybe_settings.status().ok()) {
LOG(WARNING) << "Failed to get settings for quota:"
<< maybe_settings.status().message;
return;
}
for (base::DictionaryValue::Iterator it(maybe_settings.settings());
!it.IsAtEnd(); it.Advance()) {
Allocate(it.key(), it.value(), &used_total_, &used_per_setting_);
}
usage_calculated_ = true;
}
void SettingsStorageQuotaEnforcer::Free(const std::string& key) {
if (!usage_calculated_)
return;
auto it = used_per_setting_.find(key);
if (it == used_per_setting_.end())
return;
DCHECK_GE(used_total_, it->second);
used_total_ -= it->second;
used_per_setting_.erase(it);
}
} // namespace extensions