root/extensions/browser/api/storage/settings_storage_quota_enforcer.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Allocate
  2. Free
  3. QuotaExceededError
  4. used_total_
  5. GetBytesInUse
  6. GetBytesInUse
  7. GetBytesInUse
  8. Get
  9. Get
  10. Get
  11. Remove
  12. Remove
  13. Clear
  14. Restore
  15. RestoreKey
  16. CalculateUsage

// 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 "base/bind.h"
#include "base/json/json_writer.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "extensions/browser/value_store/value_store_util.h"
#include "extensions/common/extension_api.h"

namespace util = value_store_util;

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;
}

// Frees the allocation of a setting in a record of total and per-setting usage.
void Free(
    size_t* used_total,
    std::map<std::string, size_t>* used_per_setting,
    const std::string& key) {
  *used_total -= (*used_per_setting)[key];
  used_per_setting->erase(key);
}

scoped_ptr<ValueStore::Error> QuotaExceededError(Resource resource,
                                                 scoped_ptr<std::string> key) {
  const char* name = NULL;
  // TODO(kalman): These hisograms are both silly and untracked. Fix.
  switch (resource) {
    case QUOTA_BYTES:
      name = "QUOTA_BYTES";
      UMA_HISTOGRAM_COUNTS_100(
          "Extensions.SettingsQuotaExceeded.TotalBytes", 1);
      break;
    case QUOTA_BYTES_PER_ITEM:
      name = "QUOTA_BYTES_PER_ITEM";
      UMA_HISTOGRAM_COUNTS_100(
          "Extensions.SettingsQuotaExceeded.BytesPerSetting", 1);
      break;
    case MAX_ITEMS:
      name = "MAX_ITEMS";
      UMA_HISTOGRAM_COUNTS_100(
          "Extensions.SettingsQuotaExceeded.KeyCount", 1);
      break;
  }
  CHECK(name);
  return make_scoped_ptr(new ValueStore::Error(
      ValueStore::QUOTA_EXCEEDED,
      base::StringPrintf("%s quota exceeded", name),
      key.Pass()));
}

}  // namespace

SettingsStorageQuotaEnforcer::SettingsStorageQuotaEnforcer(
    const Limits& limits, ValueStore* delegate)
    : limits_(limits), delegate_(delegate), used_total_(0) {
  CalculateUsage();
}

SettingsStorageQuotaEnforcer::~SettingsStorageQuotaEnforcer() {}

size_t SettingsStorageQuotaEnforcer::GetBytesInUse(const std::string& key) {
  std::map<std::string, size_t>::iterator 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 (std::vector<std::string>::const_iterator it = keys.begin();
      it != keys.end(); ++it) {
    used += GetBytesInUse(*it);
  }
  return used;
}

size_t SettingsStorageQuotaEnforcer::GetBytesInUse() {
  // All ValueStore implementations rely on GetBytesInUse being
  // implemented here.
  return used_total_;
}

ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
    const std::string& key) {
  return delegate_->Get(key);
}

ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
    const std::vector<std::string>& keys) {
  return delegate_->Get(keys);
}

ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get() {
  return delegate_->Get();
}

ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set(
    WriteOptions options, const std::string& key, const base::Value& value) {
  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 MakeWriteResult(
          QuotaExceededError(QUOTA_BYTES, util::NewKey(key)));
    }
    if (new_used_per_setting[key] > limits_.quota_bytes_per_item) {
      return MakeWriteResult(
          QuotaExceededError(QUOTA_BYTES_PER_ITEM, util::NewKey(key)));
    }
    if (new_used_per_setting.size() > limits_.max_items)
      return MakeWriteResult(QuotaExceededError(MAX_ITEMS, util::NewKey(key)));
  }

  WriteResult result = delegate_->Set(options, key, value);
  if (result->HasError()) {
    return result.Pass();
  }

  used_total_ = new_used_total;
  used_per_setting_.swap(new_used_per_setting);
  return result.Pass();
}

ValueStore::WriteResult SettingsStorageQuotaEnforcer::Set(
    WriteOptions options, const base::DictionaryValue& values) {
  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 MakeWriteResult(
          QuotaExceededError(QUOTA_BYTES_PER_ITEM, util::NewKey(it.key())));
    }
  }

  if (!(options & IGNORE_QUOTA)) {
    if (new_used_total > limits_.quota_bytes)
      return MakeWriteResult(QuotaExceededError(QUOTA_BYTES, util::NoKey()));
    if (new_used_per_setting.size() > limits_.max_items)
      return MakeWriteResult(QuotaExceededError(MAX_ITEMS, util::NoKey()));
  }

  WriteResult result = delegate_->Set(options, values);
  if (result->HasError()) {
    return result.Pass();
  }

  used_total_ = new_used_total;
  used_per_setting_ = new_used_per_setting;
  return result.Pass();
}

ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove(
    const std::string& key) {
  WriteResult result = delegate_->Remove(key);
  if (result->HasError()) {
    return result.Pass();
  }
  Free(&used_total_, &used_per_setting_, key);
  return result.Pass();
}

ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove(
    const std::vector<std::string>& keys) {
  WriteResult result = delegate_->Remove(keys);
  if (result->HasError()) {
    return result.Pass();
  }

  for (std::vector<std::string>::const_iterator it = keys.begin();
      it != keys.end(); ++it) {
    Free(&used_total_, &used_per_setting_, *it);
  }
  return result.Pass();
}

ValueStore::WriteResult SettingsStorageQuotaEnforcer::Clear() {
  WriteResult result = delegate_->Clear();
  if (result->HasError()) {
    return result.Pass();
  }

  used_per_setting_.clear();
  used_total_ = 0;
  return result.Pass();
}

bool SettingsStorageQuotaEnforcer::Restore() {
  if (!delegate_->Restore()) {
    // If we failed, we can't calculate the usage - that's okay, though, because
    // next time we Restore() (if it succeeds) we will recalculate usage anyway.
    // So reset storage counters now to free up resources.
    used_per_setting_.clear();
    used_total_ = 0u;
    return false;
  }
  CalculateUsage();
  return true;
}

bool SettingsStorageQuotaEnforcer::RestoreKey(const std::string& key) {
  if (!delegate_->RestoreKey(key))
    return false;

  ReadResult result = Get(key);
  // If the key was deleted as a result of the Restore() call, free it.
  if (!result->settings().HasKey(key) && ContainsKey(used_per_setting_, key))
    Free(&used_total_, &used_per_setting_, key);
  return true;
}

void SettingsStorageQuotaEnforcer::CalculateUsage() {
  ReadResult maybe_settings = delegate_->Get();
  if (maybe_settings->HasError()) {
    // Try to restore the database if it's corrupt.
    if (maybe_settings->error().code == ValueStore::CORRUPTION &&
        delegate_->Restore()) {
      maybe_settings = delegate_->Get();
    } else {
      LOG(WARNING) << "Failed to get settings for quota:"
                   << maybe_settings->error().message;
      return;
    }
  }

  for (base::DictionaryValue::Iterator it(maybe_settings->settings());
       !it.IsAtEnd();
       it.Advance()) {
    Allocate(it.key(), it.value(), &used_total_, &used_per_setting_);
  }
}

}  // namespace extensions

/* [<][>][^][v][top][bottom][index][help] */