Home | History | Annotate | Download | only in storage
      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.
      4 
      5 #include "extensions/browser/api/storage/settings_storage_quota_enforcer.h"
      6 
      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"
     16 
     17 namespace util = value_store_util;
     18 
     19 namespace extensions {
     20 
     21 namespace {
     22 
     23 // Resources there are a quota for.
     24 enum Resource {
     25   QUOTA_BYTES,
     26   QUOTA_BYTES_PER_ITEM,
     27   MAX_ITEMS
     28 };
     29 
     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];
     44 
     45   *used_total += (new_size - existing_size);
     46   (*used_per_setting)[key] = new_size;
     47 }
     48 
     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);
     56 }
     57 
     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;
     78   }
     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()));
     84 }
     85 
     86 }  // namespace
     87 
     88 SettingsStorageQuotaEnforcer::SettingsStorageQuotaEnforcer(
     89     const Limits& limits, ValueStore* delegate)
     90     : limits_(limits), delegate_(delegate), used_total_(0) {
     91   CalculateUsage();
     92 }
     93 
     94 SettingsStorageQuotaEnforcer::~SettingsStorageQuotaEnforcer() {}
     95 
     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;
    100 }
    101 
    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);
    108   }
    109   return used;
    110 }
    111 
    112 size_t SettingsStorageQuotaEnforcer::GetBytesInUse() {
    113   // All ValueStore implementations rely on GetBytesInUse being
    114   // implemented here.
    115   return used_total_;
    116 }
    117 
    118 ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
    119     const std::string& key) {
    120   return delegate_->Get(key);
    121 }
    122 
    123 ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get(
    124     const std::vector<std::string>& keys) {
    125   return delegate_->Get(keys);
    126 }
    127 
    128 ValueStore::ReadResult SettingsStorageQuotaEnforcer::Get() {
    129   return delegate_->Get();
    130 }
    131 
    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);
    137 
    138   if (!(options & IGNORE_QUOTA)) {
    139     if (new_used_total > limits_.quota_bytes) {
    140       return MakeWriteResult(
    141           QuotaExceededError(QUOTA_BYTES, util::NewKey(key)));
    142     }
    143     if (new_used_per_setting[key] > limits_.quota_bytes_per_item) {
    144       return MakeWriteResult(
    145           QuotaExceededError(QUOTA_BYTES_PER_ITEM, util::NewKey(key)));
    146     }
    147     if (new_used_per_setting.size() > limits_.max_items)
    148       return MakeWriteResult(QuotaExceededError(MAX_ITEMS, util::NewKey(key)));
    149   }
    150 
    151   WriteResult result = delegate_->Set(options, key, value);
    152   if (result->HasError()) {
    153     return result.Pass();
    154   }
    155 
    156   used_total_ = new_used_total;
    157   used_per_setting_.swap(new_used_per_setting);
    158   return result.Pass();
    159 }
    160 
    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);
    168 
    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())));
    173     }
    174   }
    175 
    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()));
    181   }
    182 
    183   WriteResult result = delegate_->Set(options, values);
    184   if (result->HasError()) {
    185     return result.Pass();
    186   }
    187 
    188   used_total_ = new_used_total;
    189   used_per_setting_ = new_used_per_setting;
    190   return result.Pass();
    191 }
    192 
    193 ValueStore::WriteResult SettingsStorageQuotaEnforcer::Remove(
    194     const std::string& key) {
    195   WriteResult result = delegate_->Remove(key);
    196   if (result->HasError()) {
    197     return result.Pass();
    198   }
    199   Free(&used_total_, &used_per_setting_, key);
    200   return result.Pass();
    201 }
    202 
    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();
    208   }
    209 
    210   for (std::vector<std::string>::const_iterator it = keys.begin();
    211       it != keys.end(); ++it) {
    212     Free(&used_total_, &used_per_setting_, *it);
    213   }
    214   return result.Pass();
    215 }
    216 
    217 ValueStore::WriteResult SettingsStorageQuotaEnforcer::Clear() {
    218   WriteResult result = delegate_->Clear();
    219   if (result->HasError()) {
    220     return result.Pass();
    221   }
    222 
    223   used_per_setting_.clear();
    224   used_total_ = 0;
    225   return result.Pass();
    226 }
    227 
    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;
    236   }
    237   CalculateUsage();
    238   return true;
    239 }
    240 
    241 bool SettingsStorageQuotaEnforcer::RestoreKey(const std::string& key) {
    242   if (!delegate_->RestoreKey(key))
    243     return false;
    244 
    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;
    250 }
    251 
    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;
    263     }
    264   }
    265 
    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_);
    270   }
    271 }
    272 
    273 }  // namespace extensions
    274