1 // Copyright 2013 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 "webkit/browser/quota/quota_manager.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/callback.h"
15 #include "base/command_line.h"
16 #include "base/file_util.h"
17 #include "base/files/file_path.h"
18 #include "base/metrics/histogram.h"
19 #include "base/sequenced_task_runner.h"
20 #include "base/single_thread_task_runner.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/sys_info.h"
23 #include "base/task_runner_util.h"
24 #include "base/time/time.h"
25 #include "net/base/net_util.h"
26 #include "webkit/browser/quota/quota_database.h"
27 #include "webkit/browser/quota/quota_manager_proxy.h"
28 #include "webkit/browser/quota/quota_temporary_storage_evictor.h"
29 #include "webkit/browser/quota/usage_tracker.h"
30 #include "webkit/common/quota/quota_types.h"
32 #define UMA_HISTOGRAM_MBYTES(name, sample) \
33 UMA_HISTOGRAM_CUSTOM_COUNTS( \
34 (name), static_cast<int>((sample) / kMBytes), \
35 1, 10 * 1024 * 1024 /* 10TB */, 100)
41 const int64 kMBytes = 1024 * 1024;
42 const int kMinutesInMilliSeconds = 60 * 1000;
44 const int64 kReportHistogramInterval = 60 * 60 * 1000; // 1 hour
45 const double kTemporaryQuotaRatioToAvail = 1.0 / 3.0; // 33%
49 // Arbitrary for now, but must be reasonably small so that
50 // in-memory databases can fit.
51 // TODO(kinuko): Refer SysInfo::AmountOfPhysicalMemory() to determine this.
52 const int64 QuotaManager::kIncognitoDefaultQuotaLimit = 100 * kMBytes;
54 const int64 QuotaManager::kNoLimit = kint64max;
56 const int QuotaManager::kPerHostTemporaryPortion = 5; // 20%
58 // Cap size for per-host persistent quota determined by the histogram.
59 // This is a bit lax value because the histogram says nothing about per-host
60 // persistent storage usage and we determined by global persistent storage
61 // usage that is less than 10GB for almost all users.
62 const int64 QuotaManager::kPerHostPersistentQuotaLimit = 10 * 1024 * kMBytes;
64 const char QuotaManager::kDatabaseName[] = "QuotaManager";
66 // Preserve kMinimumPreserveForSystem disk space for system book-keeping
67 // when returning the quota to unlimited apps/extensions.
68 // TODO(kinuko): This should be like 10% of the actual disk space.
69 // For now we simply use a constant as getting the disk size needs
70 // platform-dependent code. (http://crbug.com/178976)
71 const int64 QuotaManager::kMinimumPreserveForSystem = 1024 * kMBytes;
73 const int QuotaManager::kThresholdOfErrorsToBeBlacklisted = 3;
75 const int QuotaManager::kEvictionIntervalInMilliSeconds =
76 30 * kMinutesInMilliSeconds;
78 // Heuristics: assuming average cloud server allows a few Gigs storage
79 // on the server side and the storage needs to be shared for user data
80 // and by multiple apps.
81 int64 QuotaManager::kSyncableStorageDefaultHostQuota = 500 * kMBytes;
85 void CountOriginType(const std::set<GURL>& origins,
86 SpecialStoragePolicy* policy,
87 size_t* protected_origins,
88 size_t* unlimited_origins) {
89 DCHECK(protected_origins);
90 DCHECK(unlimited_origins);
91 *protected_origins = 0;
92 *unlimited_origins = 0;
95 for (std::set<GURL>::const_iterator itr = origins.begin();
98 if (policy->IsStorageProtected(*itr))
100 if (policy->IsStorageUnlimited(*itr))
101 ++*unlimited_origins;
105 bool SetTemporaryGlobalOverrideQuotaOnDBThread(int64* new_quota,
106 QuotaDatabase* database) {
108 if (!database->SetQuotaConfigValue(
109 QuotaDatabase::kTemporaryQuotaOverrideKey, *new_quota)) {
116 bool GetPersistentHostQuotaOnDBThread(const std::string& host,
118 QuotaDatabase* database) {
120 database->GetHostQuota(host, kStorageTypePersistent, quota);
124 bool SetPersistentHostQuotaOnDBThread(const std::string& host,
126 QuotaDatabase* database) {
128 if (database->SetHostQuota(host, kStorageTypePersistent, *new_quota))
134 bool InitializeOnDBThread(int64* temporary_quota_override,
135 int64* desired_available_space,
136 QuotaDatabase* database) {
138 database->GetQuotaConfigValue(QuotaDatabase::kTemporaryQuotaOverrideKey,
139 temporary_quota_override);
140 database->GetQuotaConfigValue(QuotaDatabase::kDesiredAvailableSpaceKey,
141 desired_available_space);
145 bool GetLRUOriginOnDBThread(StorageType type,
146 std::set<GURL>* exceptions,
147 SpecialStoragePolicy* policy,
149 QuotaDatabase* database) {
151 database->GetLRUOrigin(type, *exceptions, policy, url);
155 bool DeleteOriginInfoOnDBThread(const GURL& origin,
157 QuotaDatabase* database) {
159 return database->DeleteOriginInfo(origin, type);
162 bool InitializeTemporaryOriginsInfoOnDBThread(const std::set<GURL>* origins,
163 QuotaDatabase* database) {
165 if (database->IsOriginDatabaseBootstrapped())
168 // Register existing origins with 0 last time access.
169 if (database->RegisterInitialOriginInfo(*origins, kStorageTypeTemporary)) {
170 database->SetOriginDatabaseBootstrapped(true);
176 bool UpdateAccessTimeOnDBThread(const GURL& origin,
178 base::Time accessed_time,
179 QuotaDatabase* database) {
181 return database->SetOriginLastAccessTime(origin, type, accessed_time);
184 bool UpdateModifiedTimeOnDBThread(const GURL& origin,
186 base::Time modified_time,
187 QuotaDatabase* database) {
189 return database->SetOriginLastModifiedTime(origin, type, modified_time);
192 int64 CallSystemGetAmountOfFreeDiskSpace(const base::FilePath& profile_path) {
193 // Ensure the profile path exists.
194 if (!base::CreateDirectory(profile_path)) {
195 LOG(WARNING) << "Create directory failed for path" << profile_path.value();
198 return base::SysInfo::AmountOfFreeDiskSpace(profile_path);
201 int64 CalculateTemporaryGlobalQuota(int64 global_limited_usage,
202 int64 available_space) {
203 DCHECK_GE(global_limited_usage, 0);
204 int64 avail_space = available_space;
205 if (avail_space < kint64max - global_limited_usage) {
206 // We basically calculate the temporary quota by
207 // [available_space + space_used_for_temp] * kTempQuotaRatio,
208 // but make sure we'll have no overflow.
209 avail_space += global_limited_usage;
211 return avail_space * kTemporaryQuotaRatioToAvail;
214 void DispatchTemporaryGlobalQuotaCallback(
215 const QuotaCallback& callback,
216 QuotaStatusCode status,
217 const UsageAndQuota& usage_and_quota) {
218 if (status != kQuotaStatusOk) {
219 callback.Run(status, 0);
223 callback.Run(status, CalculateTemporaryGlobalQuota(
224 usage_and_quota.global_limited_usage,
225 usage_and_quota.available_disk_space));
228 int64 CalculateQuotaWithDiskSpace(
229 int64 available_disk_space, int64 usage, int64 quota) {
230 if (available_disk_space < QuotaManager::kMinimumPreserveForSystem ||
232 // No more space; cap the quota to the current usage.
236 available_disk_space -= QuotaManager::kMinimumPreserveForSystem;
237 if (available_disk_space < quota - usage)
238 return available_disk_space + usage;
243 int64 CalculateTemporaryHostQuota(int64 host_usage,
245 int64 global_limited_usage) {
246 DCHECK_GE(global_limited_usage, 0);
247 int64 host_quota = global_quota / QuotaManager::kPerHostTemporaryPortion;
248 if (global_limited_usage > global_quota)
249 host_quota = std::min(host_quota, host_usage);
253 void DispatchUsageAndQuotaForWebApps(
257 bool can_query_disk_size,
258 const QuotaManager::GetUsageAndQuotaCallback& callback,
259 QuotaStatusCode status,
260 const UsageAndQuota& usage_and_quota) {
261 if (status != kQuotaStatusOk) {
262 callback.Run(status, 0, 0);
266 int64 usage = usage_and_quota.usage;
267 int64 quota = usage_and_quota.quota;
269 if (type == kStorageTypeTemporary && !is_unlimited) {
270 quota = CalculateTemporaryHostQuota(
271 usage, quota, usage_and_quota.global_limited_usage);
275 quota = std::min(quota, QuotaManager::kIncognitoDefaultQuotaLimit);
276 callback.Run(status, usage, quota);
280 // For apps with unlimited permission or can_query_disk_size is true (and not
281 // in incognito mode).
282 // We assume we can expose the actual disk size for them and cap the quota by
283 // the available disk space.
284 if (is_unlimited || can_query_disk_size) {
287 CalculateQuotaWithDiskSpace(
288 usage_and_quota.available_disk_space,
293 callback.Run(status, usage, quota);
298 UsageAndQuota::UsageAndQuota()
300 global_limited_usage(0),
302 available_disk_space(0) {
305 UsageAndQuota::UsageAndQuota(
307 int64 global_limited_usage,
309 int64 available_disk_space)
311 global_limited_usage(global_limited_usage),
313 available_disk_space(available_disk_space) {
316 class UsageAndQuotaCallbackDispatcher
318 public base::SupportsWeakPtr<UsageAndQuotaCallbackDispatcher> {
320 explicit UsageAndQuotaCallbackDispatcher(QuotaManager* manager)
321 : QuotaTask(manager),
323 has_global_limited_usage_(false),
325 has_available_disk_space_(false),
326 status_(kQuotaStatusUnknown),
327 usage_and_quota_(-1, -1, -1, -1),
328 waiting_callbacks_(1) {}
330 virtual ~UsageAndQuotaCallbackDispatcher() {}
332 void WaitForResults(const QuotaManager::UsageAndQuotaCallback& callback) {
333 callback_ = callback;
337 void set_usage(int64 usage) {
338 usage_and_quota_.usage = usage;
342 void set_global_limited_usage(int64 global_limited_usage) {
343 usage_and_quota_.global_limited_usage = global_limited_usage;
344 has_global_limited_usage_ = true;
347 void set_quota(int64 quota) {
348 usage_and_quota_.quota = quota;
352 void set_available_disk_space(int64 available_disk_space) {
353 usage_and_quota_.available_disk_space = available_disk_space;
354 has_available_disk_space_ = true;
357 UsageCallback GetHostUsageCallback() {
358 ++waiting_callbacks_;
360 return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetHostUsage,
364 UsageCallback GetGlobalLimitedUsageCallback() {
365 ++waiting_callbacks_;
366 has_global_limited_usage_ = true;
368 &UsageAndQuotaCallbackDispatcher::DidGetGlobalLimitedUsage,
372 QuotaCallback GetQuotaCallback() {
373 ++waiting_callbacks_;
375 return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetQuota,
379 QuotaCallback GetAvailableSpaceCallback() {
380 ++waiting_callbacks_;
381 has_available_disk_space_ = true;
382 return base::Bind(&UsageAndQuotaCallbackDispatcher::DidGetAvailableSpace,
387 void DidGetHostUsage(int64 usage) {
388 if (status_ == kQuotaStatusUnknown)
389 status_ = kQuotaStatusOk;
390 usage_and_quota_.usage = usage;
394 void DidGetGlobalLimitedUsage(int64 limited_usage) {
395 if (status_ == kQuotaStatusUnknown)
396 status_ = kQuotaStatusOk;
397 usage_and_quota_.global_limited_usage = limited_usage;
401 void DidGetQuota(QuotaStatusCode status, int64 quota) {
402 if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk)
404 usage_and_quota_.quota = quota;
408 void DidGetAvailableSpace(QuotaStatusCode status, int64 space) {
410 if (status_ == kQuotaStatusUnknown || status_ == kQuotaStatusOk)
412 usage_and_quota_.available_disk_space = space;
416 virtual void Run() OVERRIDE {
417 // We initialize waiting_callbacks to 1 so that we won't run
418 // the completion callback until here even some of the callbacks
419 // are dispatched synchronously.
423 virtual void Aborted() OVERRIDE {
424 callback_.Run(kQuotaErrorAbort, UsageAndQuota());
428 virtual void Completed() OVERRIDE {
429 DCHECK(!has_usage_ || usage_and_quota_.usage >= 0);
430 DCHECK(!has_global_limited_usage_ ||
431 usage_and_quota_.global_limited_usage >= 0);
432 DCHECK(!has_quota_ || usage_and_quota_.quota >= 0);
433 DCHECK(!has_available_disk_space_ ||
434 usage_and_quota_.available_disk_space >= 0);
436 callback_.Run(status_, usage_and_quota_);
440 void CheckCompleted() {
441 if (--waiting_callbacks_ <= 0)
445 // For sanity checks, they're checked only when DCHECK is on.
447 bool has_global_limited_usage_;
449 bool has_available_disk_space_;
451 QuotaStatusCode status_;
452 UsageAndQuota usage_and_quota_;
453 QuotaManager::UsageAndQuotaCallback callback_;
454 int waiting_callbacks_;
456 DISALLOW_COPY_AND_ASSIGN(UsageAndQuotaCallbackDispatcher);
459 class QuotaManager::GetUsageInfoTask : public QuotaTask {
462 QuotaManager* manager,
463 const GetUsageInfoCallback& callback)
464 : QuotaTask(manager),
466 weak_factory_(this) {
470 virtual void Run() OVERRIDE {
471 remaining_trackers_ = 3;
472 // This will populate cached hosts and usage info.
473 manager()->GetUsageTracker(kStorageTypeTemporary)->GetGlobalUsage(
474 base::Bind(&GetUsageInfoTask::DidGetGlobalUsage,
475 weak_factory_.GetWeakPtr(),
476 kStorageTypeTemporary));
477 manager()->GetUsageTracker(kStorageTypePersistent)->GetGlobalUsage(
478 base::Bind(&GetUsageInfoTask::DidGetGlobalUsage,
479 weak_factory_.GetWeakPtr(),
480 kStorageTypePersistent));
481 manager()->GetUsageTracker(kStorageTypeSyncable)->GetGlobalUsage(
482 base::Bind(&GetUsageInfoTask::DidGetGlobalUsage,
483 weak_factory_.GetWeakPtr(),
484 kStorageTypeSyncable));
487 virtual void Completed() OVERRIDE {
488 callback_.Run(entries_);
492 virtual void Aborted() OVERRIDE {
493 callback_.Run(UsageInfoEntries());
498 void AddEntries(StorageType type, UsageTracker* tracker) {
499 std::map<std::string, int64> host_usage;
500 tracker->GetCachedHostsUsage(&host_usage);
501 for (std::map<std::string, int64>::const_iterator iter = host_usage.begin();
502 iter != host_usage.end();
504 entries_.push_back(UsageInfo(iter->first, type, iter->second));
506 if (--remaining_trackers_ == 0)
510 void DidGetGlobalUsage(StorageType type, int64, int64) {
511 DCHECK(manager()->GetUsageTracker(type));
512 AddEntries(type, manager()->GetUsageTracker(type));
515 QuotaManager* manager() const {
516 return static_cast<QuotaManager*>(observer());
519 GetUsageInfoCallback callback_;
520 UsageInfoEntries entries_;
521 int remaining_trackers_;
522 base::WeakPtrFactory<GetUsageInfoTask> weak_factory_;
524 DISALLOW_COPY_AND_ASSIGN(GetUsageInfoTask);
527 class QuotaManager::OriginDataDeleter : public QuotaTask {
529 OriginDataDeleter(QuotaManager* manager,
532 int quota_client_mask,
533 const StatusCallback& callback)
534 : QuotaTask(manager),
537 quota_client_mask_(quota_client_mask),
539 remaining_clients_(-1),
542 weak_factory_(this) {}
545 virtual void Run() OVERRIDE {
547 remaining_clients_ = manager()->clients_.size();
548 for (QuotaClientList::iterator iter = manager()->clients_.begin();
549 iter != manager()->clients_.end(); ++iter) {
550 if (quota_client_mask_ & (*iter)->id()) {
551 (*iter)->DeleteOriginData(
553 base::Bind(&OriginDataDeleter::DidDeleteOriginData,
554 weak_factory_.GetWeakPtr()));
557 if (--remaining_clients_ == 0)
563 virtual void Completed() OVERRIDE {
564 if (error_count_ == 0) {
565 // Only remove the entire origin if we didn't skip any client types.
566 if (skipped_clients_ == 0)
567 manager()->DeleteOriginFromDatabase(origin_, type_);
568 callback_.Run(kQuotaStatusOk);
570 callback_.Run(kQuotaErrorInvalidModification);
575 virtual void Aborted() OVERRIDE {
576 callback_.Run(kQuotaErrorAbort);
581 void DidDeleteOriginData(QuotaStatusCode status) {
582 DCHECK_GT(remaining_clients_, 0);
584 if (status != kQuotaStatusOk)
587 if (--remaining_clients_ == 0)
591 QuotaManager* manager() const {
592 return static_cast<QuotaManager*>(observer());
597 int quota_client_mask_;
599 int remaining_clients_;
600 int skipped_clients_;
601 StatusCallback callback_;
603 base::WeakPtrFactory<OriginDataDeleter> weak_factory_;
604 DISALLOW_COPY_AND_ASSIGN(OriginDataDeleter);
607 class QuotaManager::HostDataDeleter : public QuotaTask {
609 HostDataDeleter(QuotaManager* manager,
610 const std::string& host,
612 int quota_client_mask,
613 const StatusCallback& callback)
614 : QuotaTask(manager),
617 quota_client_mask_(quota_client_mask),
619 remaining_clients_(-1),
620 remaining_deleters_(-1),
622 weak_factory_(this) {}
625 virtual void Run() OVERRIDE {
627 remaining_clients_ = manager()->clients_.size();
628 for (QuotaClientList::iterator iter = manager()->clients_.begin();
629 iter != manager()->clients_.end(); ++iter) {
630 (*iter)->GetOriginsForHost(
632 base::Bind(&HostDataDeleter::DidGetOriginsForHost,
633 weak_factory_.GetWeakPtr()));
637 virtual void Completed() OVERRIDE {
638 if (error_count_ == 0) {
639 callback_.Run(kQuotaStatusOk);
641 callback_.Run(kQuotaErrorInvalidModification);
646 virtual void Aborted() OVERRIDE {
647 callback_.Run(kQuotaErrorAbort);
652 void DidGetOriginsForHost(const std::set<GURL>& origins) {
653 DCHECK_GT(remaining_clients_, 0);
655 origins_.insert(origins.begin(), origins.end());
657 if (--remaining_clients_ == 0) {
658 if (!origins_.empty())
659 ScheduleOriginsDeletion();
665 void ScheduleOriginsDeletion() {
666 remaining_deleters_ = origins_.size();
667 for (std::set<GURL>::const_iterator p = origins_.begin();
670 OriginDataDeleter* deleter =
671 new OriginDataDeleter(
672 manager(), *p, type_, quota_client_mask_,
673 base::Bind(&HostDataDeleter::DidDeleteOriginData,
674 weak_factory_.GetWeakPtr()));
679 void DidDeleteOriginData(QuotaStatusCode status) {
680 DCHECK_GT(remaining_deleters_, 0);
682 if (status != kQuotaStatusOk)
685 if (--remaining_deleters_ == 0)
689 QuotaManager* manager() const {
690 return static_cast<QuotaManager*>(observer());
695 int quota_client_mask_;
696 std::set<GURL> origins_;
698 int remaining_clients_;
699 int remaining_deleters_;
700 StatusCallback callback_;
702 base::WeakPtrFactory<HostDataDeleter> weak_factory_;
703 DISALLOW_COPY_AND_ASSIGN(HostDataDeleter);
706 class QuotaManager::GetModifiedSinceHelper {
708 bool GetModifiedSinceOnDBThread(StorageType type,
709 base::Time modified_since,
710 QuotaDatabase* database) {
712 return database->GetOriginsModifiedSince(type, &origins_, modified_since);
715 void DidGetModifiedSince(const base::WeakPtr<QuotaManager>& manager,
716 const GetOriginsCallback& callback,
720 // The operation was aborted.
721 callback.Run(std::set<GURL>(), type);
724 manager->DidDatabaseWork(success);
725 callback.Run(origins_, type);
729 std::set<GURL> origins_;
732 class QuotaManager::DumpQuotaTableHelper {
734 bool DumpQuotaTableOnDBThread(QuotaDatabase* database) {
736 return database->DumpQuotaTable(
737 base::Bind(&DumpQuotaTableHelper::AppendEntry, base::Unretained(this)));
740 void DidDumpQuotaTable(const base::WeakPtr<QuotaManager>& manager,
741 const DumpQuotaTableCallback& callback,
744 // The operation was aborted.
745 callback.Run(QuotaTableEntries());
748 manager->DidDatabaseWork(success);
749 callback.Run(entries_);
753 bool AppendEntry(const QuotaTableEntry& entry) {
754 entries_.push_back(entry);
758 QuotaTableEntries entries_;
761 class QuotaManager::DumpOriginInfoTableHelper {
763 bool DumpOriginInfoTableOnDBThread(QuotaDatabase* database) {
765 return database->DumpOriginInfoTable(
766 base::Bind(&DumpOriginInfoTableHelper::AppendEntry,
767 base::Unretained(this)));
770 void DidDumpOriginInfoTable(const base::WeakPtr<QuotaManager>& manager,
771 const DumpOriginInfoTableCallback& callback,
774 // The operation was aborted.
775 callback.Run(OriginInfoTableEntries());
778 manager->DidDatabaseWork(success);
779 callback.Run(entries_);
783 bool AppendEntry(const OriginInfoTableEntry& entry) {
784 entries_.push_back(entry);
788 OriginInfoTableEntries entries_;
791 // QuotaManager ---------------------------------------------------------------
793 QuotaManager::QuotaManager(bool is_incognito,
794 const base::FilePath& profile_path,
795 base::SingleThreadTaskRunner* io_thread,
796 base::SequencedTaskRunner* db_thread,
797 SpecialStoragePolicy* special_storage_policy)
798 : is_incognito_(is_incognito),
799 profile_path_(profile_path),
800 proxy_(new QuotaManagerProxy(
803 eviction_disabled_(false),
804 io_thread_(io_thread),
805 db_thread_(db_thread),
806 temporary_quota_initialized_(false),
807 temporary_quota_override_(-1),
808 desired_available_space_(-1),
809 special_storage_policy_(special_storage_policy),
810 get_disk_space_fn_(&CallSystemGetAmountOfFreeDiskSpace),
811 weak_factory_(this) {
814 void QuotaManager::GetUsageInfo(const GetUsageInfoCallback& callback) {
816 GetUsageInfoTask* get_usage_info = new GetUsageInfoTask(this, callback);
817 get_usage_info->Start();
820 void QuotaManager::GetUsageAndQuotaForWebApps(
823 const GetUsageAndQuotaCallback& callback) {
824 if (type != kStorageTypeTemporary &&
825 type != kStorageTypePersistent &&
826 type != kStorageTypeSyncable) {
827 callback.Run(kQuotaErrorNotSupported, 0, 0);
831 DCHECK(origin == origin.GetOrigin());
834 bool unlimited = IsStorageUnlimited(origin, type);
835 bool can_query_disk_size = CanQueryDiskSize(origin);
837 UsageAndQuotaCallbackDispatcher* dispatcher =
838 new UsageAndQuotaCallbackDispatcher(this);
840 UsageAndQuota usage_and_quota;
842 dispatcher->set_quota(kNoLimit);
844 if (type == kStorageTypeTemporary) {
845 GetUsageTracker(type)->GetGlobalLimitedUsage(
846 dispatcher->GetGlobalLimitedUsageCallback());
847 GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback());
848 } else if (type == kStorageTypePersistent) {
849 GetPersistentHostQuota(net::GetHostOrSpecFromURL(origin),
850 dispatcher->GetQuotaCallback());
852 dispatcher->set_quota(kSyncableStorageDefaultHostQuota);
856 DCHECK(GetUsageTracker(type));
857 GetUsageTracker(type)->GetHostUsage(net::GetHostOrSpecFromURL(origin),
858 dispatcher->GetHostUsageCallback());
860 if (!is_incognito_ && (unlimited || can_query_disk_size))
861 GetAvailableSpace(dispatcher->GetAvailableSpaceCallback());
863 dispatcher->WaitForResults(base::Bind(
864 &DispatchUsageAndQuotaForWebApps,
865 type, is_incognito_, unlimited, can_query_disk_size,
869 void QuotaManager::GetUsageAndQuota(
870 const GURL& origin, StorageType type,
871 const GetUsageAndQuotaCallback& callback) {
872 DCHECK(origin == origin.GetOrigin());
874 if (IsStorageUnlimited(origin, type)) {
875 callback.Run(kQuotaStatusOk, 0, kNoLimit);
879 GetUsageAndQuotaForWebApps(origin, type, callback);
882 void QuotaManager::NotifyStorageAccessed(
883 QuotaClient::ID client_id,
884 const GURL& origin, StorageType type) {
885 DCHECK(origin == origin.GetOrigin());
886 NotifyStorageAccessedInternal(client_id, origin, type, base::Time::Now());
889 void QuotaManager::NotifyStorageModified(
890 QuotaClient::ID client_id,
891 const GURL& origin, StorageType type, int64 delta) {
892 DCHECK(origin == origin.GetOrigin());
893 NotifyStorageModifiedInternal(client_id, origin, type, delta,
897 void QuotaManager::NotifyOriginInUse(const GURL& origin) {
898 DCHECK(io_thread_->BelongsToCurrentThread());
899 origins_in_use_[origin]++;
902 void QuotaManager::NotifyOriginNoLongerInUse(const GURL& origin) {
903 DCHECK(io_thread_->BelongsToCurrentThread());
904 DCHECK(IsOriginInUse(origin));
905 int& count = origins_in_use_[origin];
907 origins_in_use_.erase(origin);
910 void QuotaManager::SetUsageCacheEnabled(QuotaClient::ID client_id,
915 DCHECK(GetUsageTracker(type));
916 GetUsageTracker(type)->SetUsageCacheEnabled(client_id, origin, enabled);
919 void QuotaManager::DeleteOriginData(
920 const GURL& origin, StorageType type, int quota_client_mask,
921 const StatusCallback& callback) {
924 if (origin.is_empty() || clients_.empty()) {
925 callback.Run(kQuotaStatusOk);
929 DCHECK(origin == origin.GetOrigin());
930 OriginDataDeleter* deleter =
931 new OriginDataDeleter(this, origin, type, quota_client_mask, callback);
935 void QuotaManager::DeleteHostData(const std::string& host,
937 int quota_client_mask,
938 const StatusCallback& callback) {
941 if (host.empty() || clients_.empty()) {
942 callback.Run(kQuotaStatusOk);
946 HostDataDeleter* deleter =
947 new HostDataDeleter(this, host, type, quota_client_mask, callback);
951 void QuotaManager::GetAvailableSpace(const AvailableSpaceCallback& callback) {
952 if (!available_space_callbacks_.Add(callback))
955 PostTaskAndReplyWithResult(db_thread_.get(),
957 base::Bind(get_disk_space_fn_, profile_path_),
958 base::Bind(&QuotaManager::DidGetAvailableSpace,
959 weak_factory_.GetWeakPtr()));
962 void QuotaManager::GetTemporaryGlobalQuota(const QuotaCallback& callback) {
964 if (!temporary_quota_initialized_) {
965 db_initialization_callbacks_.Add(base::Bind(
966 &QuotaManager::GetTemporaryGlobalQuota,
967 weak_factory_.GetWeakPtr(), callback));
971 if (temporary_quota_override_ > 0) {
972 callback.Run(kQuotaStatusOk, temporary_quota_override_);
976 UsageAndQuotaCallbackDispatcher* dispatcher =
977 new UsageAndQuotaCallbackDispatcher(this);
978 GetUsageTracker(kStorageTypeTemporary)->
979 GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback());
980 GetAvailableSpace(dispatcher->GetAvailableSpaceCallback());
981 dispatcher->WaitForResults(
982 base::Bind(&DispatchTemporaryGlobalQuotaCallback, callback));
985 void QuotaManager::SetTemporaryGlobalOverrideQuota(
986 int64 new_quota, const QuotaCallback& callback) {
990 if (!callback.is_null())
991 callback.Run(kQuotaErrorInvalidModification, -1);
996 if (!callback.is_null())
997 callback.Run(kQuotaErrorInvalidAccess, -1);
1001 int64* new_quota_ptr = new int64(new_quota);
1002 PostTaskAndReplyWithResultForDBThread(
1004 base::Bind(&SetTemporaryGlobalOverrideQuotaOnDBThread,
1005 base::Unretained(new_quota_ptr)),
1006 base::Bind(&QuotaManager::DidSetTemporaryGlobalOverrideQuota,
1007 weak_factory_.GetWeakPtr(),
1009 base::Owned(new_quota_ptr)));
1012 void QuotaManager::GetPersistentHostQuota(const std::string& host,
1013 const QuotaCallback& callback) {
1016 // This could happen if we are called on file:///.
1017 // TODO(kinuko) We may want to respect --allow-file-access-from-files
1018 // command line switch.
1019 callback.Run(kQuotaStatusOk, 0);
1023 if (!persistent_host_quota_callbacks_.Add(host, callback))
1026 int64* quota_ptr = new int64(0);
1027 PostTaskAndReplyWithResultForDBThread(
1029 base::Bind(&GetPersistentHostQuotaOnDBThread,
1031 base::Unretained(quota_ptr)),
1032 base::Bind(&QuotaManager::DidGetPersistentHostQuota,
1033 weak_factory_.GetWeakPtr(),
1035 base::Owned(quota_ptr)));
1038 void QuotaManager::SetPersistentHostQuota(const std::string& host,
1040 const QuotaCallback& callback) {
1043 // This could happen if we are called on file:///.
1044 callback.Run(kQuotaErrorNotSupported, 0);
1048 if (new_quota < 0) {
1049 callback.Run(kQuotaErrorInvalidModification, -1);
1053 if (kPerHostPersistentQuotaLimit < new_quota) {
1054 // Cap the requested size at the per-host quota limit.
1055 new_quota = kPerHostPersistentQuotaLimit;
1059 callback.Run(kQuotaErrorInvalidAccess, -1);
1063 int64* new_quota_ptr = new int64(new_quota);
1064 PostTaskAndReplyWithResultForDBThread(
1066 base::Bind(&SetPersistentHostQuotaOnDBThread,
1068 base::Unretained(new_quota_ptr)),
1069 base::Bind(&QuotaManager::DidSetPersistentHostQuota,
1070 weak_factory_.GetWeakPtr(),
1073 base::Owned(new_quota_ptr)));
1076 void QuotaManager::GetGlobalUsage(StorageType type,
1077 const GlobalUsageCallback& callback) {
1079 DCHECK(GetUsageTracker(type));
1080 GetUsageTracker(type)->GetGlobalUsage(callback);
1083 void QuotaManager::GetHostUsage(const std::string& host,
1085 const UsageCallback& callback) {
1087 DCHECK(GetUsageTracker(type));
1088 GetUsageTracker(type)->GetHostUsage(host, callback);
1091 void QuotaManager::GetHostUsage(const std::string& host,
1093 QuotaClient::ID client_id,
1094 const UsageCallback& callback) {
1096 DCHECK(GetUsageTracker(type));
1097 ClientUsageTracker* tracker =
1098 GetUsageTracker(type)->GetClientTracker(client_id);
1103 tracker->GetHostUsage(host, callback);
1106 bool QuotaManager::IsTrackingHostUsage(StorageType type,
1107 QuotaClient::ID client_id) const {
1108 UsageTracker* tracker = GetUsageTracker(type);
1109 return tracker && tracker->GetClientTracker(client_id);
1112 void QuotaManager::GetStatistics(
1113 std::map<std::string, std::string>* statistics) {
1115 if (temporary_storage_evictor_) {
1116 std::map<std::string, int64> stats;
1117 temporary_storage_evictor_->GetStatistics(&stats);
1118 for (std::map<std::string, int64>::iterator p = stats.begin();
1121 (*statistics)[p->first] = base::Int64ToString(p->second);
1125 bool QuotaManager::IsStorageUnlimited(const GURL& origin,
1126 StorageType type) const {
1127 // For syncable storage we should always enforce quota (since the
1128 // quota must be capped by the server limit).
1129 if (type == kStorageTypeSyncable)
1131 if (type == kStorageTypeQuotaNotManaged)
1133 return special_storage_policy_.get() &&
1134 special_storage_policy_->IsStorageUnlimited(origin);
1137 void QuotaManager::GetOriginsModifiedSince(StorageType type,
1138 base::Time modified_since,
1139 const GetOriginsCallback& callback) {
1141 GetModifiedSinceHelper* helper = new GetModifiedSinceHelper;
1142 PostTaskAndReplyWithResultForDBThread(
1144 base::Bind(&GetModifiedSinceHelper::GetModifiedSinceOnDBThread,
1145 base::Unretained(helper),
1148 base::Bind(&GetModifiedSinceHelper::DidGetModifiedSince,
1149 base::Owned(helper),
1150 weak_factory_.GetWeakPtr(),
1155 bool QuotaManager::ResetUsageTracker(StorageType type) {
1156 DCHECK(GetUsageTracker(type));
1157 if (GetUsageTracker(type)->IsWorking())
1160 case kStorageTypeTemporary:
1161 temporary_usage_tracker_.reset(new UsageTracker(
1162 clients_, kStorageTypeTemporary, special_storage_policy_.get()));
1164 case kStorageTypePersistent:
1165 persistent_usage_tracker_.reset(new UsageTracker(
1166 clients_, kStorageTypePersistent, special_storage_policy_.get()));
1168 case kStorageTypeSyncable:
1169 syncable_usage_tracker_.reset(new UsageTracker(
1170 clients_, kStorageTypeSyncable, special_storage_policy_.get()));
1178 QuotaManager::~QuotaManager() {
1179 proxy_->manager_ = NULL;
1180 std::for_each(clients_.begin(), clients_.end(),
1181 std::mem_fun(&QuotaClient::OnQuotaManagerDestroyed));
1183 db_thread_->DeleteSoon(FROM_HERE, database_.release());
1186 QuotaManager::EvictionContext::EvictionContext()
1187 : evicted_type(kStorageTypeUnknown) {
1190 QuotaManager::EvictionContext::~EvictionContext() {
1193 void QuotaManager::LazyInitialize() {
1194 DCHECK(io_thread_->BelongsToCurrentThread());
1196 // Initialization seems to be done already.
1200 // Use an empty path to open an in-memory only databse for incognito.
1201 database_.reset(new QuotaDatabase(is_incognito_ ? base::FilePath() :
1202 profile_path_.AppendASCII(kDatabaseName)));
1204 temporary_usage_tracker_.reset(new UsageTracker(
1205 clients_, kStorageTypeTemporary, special_storage_policy_.get()));
1206 persistent_usage_tracker_.reset(new UsageTracker(
1207 clients_, kStorageTypePersistent, special_storage_policy_.get()));
1208 syncable_usage_tracker_.reset(new UsageTracker(
1209 clients_, kStorageTypeSyncable, special_storage_policy_.get()));
1211 int64* temporary_quota_override = new int64(-1);
1212 int64* desired_available_space = new int64(-1);
1213 PostTaskAndReplyWithResultForDBThread(
1215 base::Bind(&InitializeOnDBThread,
1216 base::Unretained(temporary_quota_override),
1217 base::Unretained(desired_available_space)),
1218 base::Bind(&QuotaManager::DidInitialize,
1219 weak_factory_.GetWeakPtr(),
1220 base::Owned(temporary_quota_override),
1221 base::Owned(desired_available_space)));
1224 void QuotaManager::RegisterClient(QuotaClient* client) {
1225 DCHECK(!database_.get());
1226 clients_.push_back(client);
1229 UsageTracker* QuotaManager::GetUsageTracker(StorageType type) const {
1231 case kStorageTypeTemporary:
1232 return temporary_usage_tracker_.get();
1233 case kStorageTypePersistent:
1234 return persistent_usage_tracker_.get();
1235 case kStorageTypeSyncable:
1236 return syncable_usage_tracker_.get();
1237 case kStorageTypeQuotaNotManaged:
1239 case kStorageTypeUnknown:
1245 void QuotaManager::GetCachedOrigins(
1246 StorageType type, std::set<GURL>* origins) {
1249 DCHECK(GetUsageTracker(type));
1250 GetUsageTracker(type)->GetCachedOrigins(origins);
1253 void QuotaManager::NotifyStorageAccessedInternal(
1254 QuotaClient::ID client_id,
1255 const GURL& origin, StorageType type,
1256 base::Time accessed_time) {
1258 if (type == kStorageTypeTemporary && !lru_origin_callback_.is_null()) {
1259 // Record the accessed origins while GetLRUOrigin task is runing
1260 // to filter out them from eviction.
1261 access_notified_origins_.insert(origin);
1266 PostTaskAndReplyWithResultForDBThread(
1268 base::Bind(&UpdateAccessTimeOnDBThread, origin, type, accessed_time),
1269 base::Bind(&QuotaManager::DidDatabaseWork,
1270 weak_factory_.GetWeakPtr()));
1273 void QuotaManager::NotifyStorageModifiedInternal(
1274 QuotaClient::ID client_id,
1278 base::Time modified_time) {
1280 DCHECK(GetUsageTracker(type));
1281 GetUsageTracker(type)->UpdateUsageCache(client_id, origin, delta);
1283 PostTaskAndReplyWithResultForDBThread(
1285 base::Bind(&UpdateModifiedTimeOnDBThread, origin, type, modified_time),
1286 base::Bind(&QuotaManager::DidDatabaseWork,
1287 weak_factory_.GetWeakPtr()));
1290 void QuotaManager::DumpQuotaTable(const DumpQuotaTableCallback& callback) {
1291 DumpQuotaTableHelper* helper = new DumpQuotaTableHelper;
1292 PostTaskAndReplyWithResultForDBThread(
1294 base::Bind(&DumpQuotaTableHelper::DumpQuotaTableOnDBThread,
1295 base::Unretained(helper)),
1296 base::Bind(&DumpQuotaTableHelper::DidDumpQuotaTable,
1297 base::Owned(helper),
1298 weak_factory_.GetWeakPtr(),
1302 void QuotaManager::DumpOriginInfoTable(
1303 const DumpOriginInfoTableCallback& callback) {
1304 DumpOriginInfoTableHelper* helper = new DumpOriginInfoTableHelper;
1305 PostTaskAndReplyWithResultForDBThread(
1307 base::Bind(&DumpOriginInfoTableHelper::DumpOriginInfoTableOnDBThread,
1308 base::Unretained(helper)),
1309 base::Bind(&DumpOriginInfoTableHelper::DidDumpOriginInfoTable,
1310 base::Owned(helper),
1311 weak_factory_.GetWeakPtr(),
1315 void QuotaManager::StartEviction() {
1316 DCHECK(!temporary_storage_evictor_.get());
1317 temporary_storage_evictor_.reset(new QuotaTemporaryStorageEvictor(
1318 this, kEvictionIntervalInMilliSeconds));
1319 if (desired_available_space_ >= 0)
1320 temporary_storage_evictor_->set_min_available_disk_space_to_start_eviction(
1321 desired_available_space_);
1322 temporary_storage_evictor_->Start();
1325 void QuotaManager::DeleteOriginFromDatabase(
1326 const GURL& origin, StorageType type) {
1331 PostTaskAndReplyWithResultForDBThread(
1333 base::Bind(&DeleteOriginInfoOnDBThread, origin, type),
1334 base::Bind(&QuotaManager::DidDatabaseWork,
1335 weak_factory_.GetWeakPtr()));
1338 void QuotaManager::DidOriginDataEvicted(QuotaStatusCode status) {
1339 DCHECK(io_thread_->BelongsToCurrentThread());
1341 // We only try evict origins that are not in use, so basically
1342 // deletion attempt for eviction should not fail. Let's record
1343 // the origin if we get error and exclude it from future eviction
1344 // if the error happens consistently (> kThresholdOfErrorsToBeBlacklisted).
1345 if (status != kQuotaStatusOk)
1346 origins_in_error_[eviction_context_.evicted_origin]++;
1348 eviction_context_.evict_origin_data_callback.Run(status);
1349 eviction_context_.evict_origin_data_callback.Reset();
1352 void QuotaManager::ReportHistogram() {
1353 GetGlobalUsage(kStorageTypeTemporary,
1355 &QuotaManager::DidGetTemporaryGlobalUsageForHistogram,
1356 weak_factory_.GetWeakPtr()));
1357 GetGlobalUsage(kStorageTypePersistent,
1359 &QuotaManager::DidGetPersistentGlobalUsageForHistogram,
1360 weak_factory_.GetWeakPtr()));
1363 void QuotaManager::DidGetTemporaryGlobalUsageForHistogram(
1365 int64 unlimited_usage) {
1366 UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfTemporaryStorage", usage);
1368 std::set<GURL> origins;
1369 GetCachedOrigins(kStorageTypeTemporary, &origins);
1371 size_t num_origins = origins.size();
1372 size_t protected_origins = 0;
1373 size_t unlimited_origins = 0;
1374 CountOriginType(origins,
1375 special_storage_policy_.get(),
1377 &unlimited_origins);
1379 UMA_HISTOGRAM_COUNTS("Quota.NumberOfTemporaryStorageOrigins",
1381 UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedTemporaryStorageOrigins",
1383 UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedTemporaryStorageOrigins",
1387 void QuotaManager::DidGetPersistentGlobalUsageForHistogram(
1389 int64 unlimited_usage) {
1390 UMA_HISTOGRAM_MBYTES("Quota.GlobalUsageOfPersistentStorage", usage);
1392 std::set<GURL> origins;
1393 GetCachedOrigins(kStorageTypePersistent, &origins);
1395 size_t num_origins = origins.size();
1396 size_t protected_origins = 0;
1397 size_t unlimited_origins = 0;
1398 CountOriginType(origins,
1399 special_storage_policy_.get(),
1401 &unlimited_origins);
1403 UMA_HISTOGRAM_COUNTS("Quota.NumberOfPersistentStorageOrigins",
1405 UMA_HISTOGRAM_COUNTS("Quota.NumberOfProtectedPersistentStorageOrigins",
1407 UMA_HISTOGRAM_COUNTS("Quota.NumberOfUnlimitedPersistentStorageOrigins",
1411 void QuotaManager::GetLRUOrigin(
1413 const GetLRUOriginCallback& callback) {
1415 // This must not be called while there's an in-flight task.
1416 DCHECK(lru_origin_callback_.is_null());
1417 lru_origin_callback_ = callback;
1419 lru_origin_callback_.Run(GURL());
1420 lru_origin_callback_.Reset();
1424 std::set<GURL>* exceptions = new std::set<GURL>;
1425 for (std::map<GURL, int>::const_iterator p = origins_in_use_.begin();
1426 p != origins_in_use_.end();
1429 exceptions->insert(p->first);
1431 for (std::map<GURL, int>::const_iterator p = origins_in_error_.begin();
1432 p != origins_in_error_.end();
1434 if (p->second > QuotaManager::kThresholdOfErrorsToBeBlacklisted)
1435 exceptions->insert(p->first);
1438 GURL* url = new GURL;
1439 PostTaskAndReplyWithResultForDBThread(
1441 base::Bind(&GetLRUOriginOnDBThread,
1443 base::Owned(exceptions),
1444 special_storage_policy_,
1445 base::Unretained(url)),
1446 base::Bind(&QuotaManager::DidGetLRUOrigin,
1447 weak_factory_.GetWeakPtr(),
1451 void QuotaManager::EvictOriginData(
1454 const EvictOriginDataCallback& callback) {
1455 DCHECK(io_thread_->BelongsToCurrentThread());
1456 DCHECK_EQ(type, kStorageTypeTemporary);
1458 eviction_context_.evicted_origin = origin;
1459 eviction_context_.evicted_type = type;
1460 eviction_context_.evict_origin_data_callback = callback;
1462 DeleteOriginData(origin, type, QuotaClient::kAllClientsMask,
1463 base::Bind(&QuotaManager::DidOriginDataEvicted,
1464 weak_factory_.GetWeakPtr()));
1467 void QuotaManager::GetUsageAndQuotaForEviction(
1468 const UsageAndQuotaCallback& callback) {
1469 DCHECK(io_thread_->BelongsToCurrentThread());
1472 UsageAndQuotaCallbackDispatcher* dispatcher =
1473 new UsageAndQuotaCallbackDispatcher(this);
1474 GetUsageTracker(kStorageTypeTemporary)->
1475 GetGlobalLimitedUsage(dispatcher->GetGlobalLimitedUsageCallback());
1476 GetTemporaryGlobalQuota(dispatcher->GetQuotaCallback());
1477 GetAvailableSpace(dispatcher->GetAvailableSpaceCallback());
1478 dispatcher->WaitForResults(callback);
1481 void QuotaManager::DidSetTemporaryGlobalOverrideQuota(
1482 const QuotaCallback& callback,
1483 const int64* new_quota,
1485 QuotaStatusCode status = kQuotaErrorInvalidAccess;
1486 DidDatabaseWork(success);
1488 temporary_quota_override_ = *new_quota;
1489 status = kQuotaStatusOk;
1492 if (callback.is_null())
1495 callback.Run(status, *new_quota);
1498 void QuotaManager::DidGetPersistentHostQuota(const std::string& host,
1501 DidDatabaseWork(success);
1502 persistent_host_quota_callbacks_.Run(
1503 host, MakeTuple(kQuotaStatusOk, *quota));
1506 void QuotaManager::DidSetPersistentHostQuota(const std::string& host,
1507 const QuotaCallback& callback,
1508 const int64* new_quota,
1510 DidDatabaseWork(success);
1511 callback.Run(success ? kQuotaStatusOk : kQuotaErrorInvalidAccess, *new_quota);
1514 void QuotaManager::DidInitialize(int64* temporary_quota_override,
1515 int64* desired_available_space,
1517 temporary_quota_override_ = *temporary_quota_override;
1518 desired_available_space_ = *desired_available_space;
1519 temporary_quota_initialized_ = true;
1520 DidDatabaseWork(success);
1522 histogram_timer_.Start(FROM_HERE,
1523 base::TimeDelta::FromMilliseconds(
1524 kReportHistogramInterval),
1525 this, &QuotaManager::ReportHistogram);
1527 db_initialization_callbacks_.Run(MakeTuple());
1528 GetTemporaryGlobalQuota(
1529 base::Bind(&QuotaManager::DidGetInitialTemporaryGlobalQuota,
1530 weak_factory_.GetWeakPtr()));
1533 void QuotaManager::DidGetLRUOrigin(const GURL* origin,
1535 DidDatabaseWork(success);
1536 // Make sure the returned origin is (still) not in the origin_in_use_ set
1537 // and has not been accessed since we posted the task.
1538 if (origins_in_use_.find(*origin) != origins_in_use_.end() ||
1539 access_notified_origins_.find(*origin) != access_notified_origins_.end())
1540 lru_origin_callback_.Run(GURL());
1542 lru_origin_callback_.Run(*origin);
1543 access_notified_origins_.clear();
1544 lru_origin_callback_.Reset();
1547 void QuotaManager::DidGetInitialTemporaryGlobalQuota(
1548 QuotaStatusCode status, int64 quota_unused) {
1549 if (eviction_disabled_)
1552 std::set<GURL>* origins = new std::set<GURL>;
1553 temporary_usage_tracker_->GetCachedOrigins(origins);
1554 // This will call the StartEviction() when initial origin registration
1556 PostTaskAndReplyWithResultForDBThread(
1558 base::Bind(&InitializeTemporaryOriginsInfoOnDBThread,
1559 base::Owned(origins)),
1560 base::Bind(&QuotaManager::DidInitializeTemporaryOriginsInfo,
1561 weak_factory_.GetWeakPtr()));
1564 void QuotaManager::DidInitializeTemporaryOriginsInfo(bool success) {
1565 DidDatabaseWork(success);
1570 void QuotaManager::DidGetAvailableSpace(int64 space) {
1571 available_space_callbacks_.Run(MakeTuple(kQuotaStatusOk, space));
1574 void QuotaManager::DidDatabaseWork(bool success) {
1575 db_disabled_ = !success;
1578 void QuotaManager::DeleteOnCorrectThread() const {
1579 if (!io_thread_->BelongsToCurrentThread() &&
1580 io_thread_->DeleteSoon(FROM_HERE, this)) {
1586 void QuotaManager::PostTaskAndReplyWithResultForDBThread(
1587 const tracked_objects::Location& from_here,
1588 const base::Callback<bool(QuotaDatabase*)>& task,
1589 const base::Callback<void(bool)>& reply) {
1590 // Deleting manager will post another task to DB thread to delete
1591 // |database_|, therefore we can be sure that database_ is alive when this
1593 base::PostTaskAndReplyWithResult(
1596 base::Bind(task, base::Unretained(database_.get())),
1600 } // namespace quota