1 // Copyright (c) 2012 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/appcache/appcache_storage_impl.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/file_util.h"
15 #include "base/logging.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_util.h"
19 #include "net/base/cache_type.h"
20 #include "net/base/net_errors.h"
21 #include "sql/connection.h"
22 #include "sql/transaction.h"
23 #include "webkit/browser/appcache/appcache.h"
24 #include "webkit/browser/appcache/appcache_database.h"
25 #include "webkit/browser/appcache/appcache_entry.h"
26 #include "webkit/browser/appcache/appcache_group.h"
27 #include "webkit/browser/appcache/appcache_histograms.h"
28 #include "webkit/browser/appcache/appcache_quota_client.h"
29 #include "webkit/browser/appcache/appcache_response.h"
30 #include "webkit/browser/appcache/appcache_service.h"
31 #include "webkit/browser/quota/quota_client.h"
32 #include "webkit/browser/quota/quota_manager.h"
33 #include "webkit/browser/quota/special_storage_policy.h"
37 // Hard coded default when not using quota management.
38 static const int kDefaultQuota = 5 * 1024 * 1024;
40 static const int kMaxDiskCacheSize = 250 * 1024 * 1024;
41 static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024;
42 static const base::FilePath::CharType kDiskCacheDirectoryName[] =
43 FILE_PATH_LITERAL("Cache");
47 // Helpers for clearing data from the AppCacheDatabase.
48 bool DeleteGroupAndRelatedRecords(AppCacheDatabase* database,
50 std::vector<int64>* deletable_response_ids) {
51 AppCacheDatabase::CacheRecord cache_record;
53 if (database->FindCacheForGroup(group_id, &cache_record)) {
54 database->FindResponseIdsForCacheAsVector(cache_record.cache_id,
55 deletable_response_ids);
57 database->DeleteGroup(group_id) &&
58 database->DeleteCache(cache_record.cache_id) &&
59 database->DeleteEntriesForCache(cache_record.cache_id) &&
60 database->DeleteNamespacesForCache(cache_record.cache_id) &&
61 database->DeleteOnlineWhiteListForCache(cache_record.cache_id) &&
62 database->InsertDeletableResponseIds(*deletable_response_ids);
64 NOTREACHED() << "A existing group without a cache is unexpected";
65 success = database->DeleteGroup(group_id);
70 // Destroys |database|. If there is appcache data to be deleted
71 // (|force_keep_session_state| is false), deletes session-only appcache data.
72 void ClearSessionOnlyOrigins(
73 AppCacheDatabase* database,
74 scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy,
75 bool force_keep_session_state) {
76 scoped_ptr<AppCacheDatabase> database_to_delete(database);
78 // If saving session state, only delete the database.
79 if (force_keep_session_state)
82 bool has_session_only_appcaches =
83 special_storage_policy.get() &&
84 special_storage_policy->HasSessionOnlyOrigins();
86 // Clearning only session-only databases, and there are none.
87 if (!has_session_only_appcaches)
90 std::set<GURL> origins;
91 database->FindOriginsWithGroups(&origins);
93 return; // nothing to delete
95 sql::Connection* connection = database->db_connection();
97 NOTREACHED() << "Missing database connection.";
101 std::set<GURL>::const_iterator origin;
102 for (origin = origins.begin(); origin != origins.end(); ++origin) {
103 if (!special_storage_policy->IsStorageSessionOnly(*origin))
105 if (special_storage_policy.get() &&
106 special_storage_policy->IsStorageProtected(*origin))
109 std::vector<AppCacheDatabase::GroupRecord> groups;
110 database->FindGroupsForOrigin(*origin, &groups);
111 std::vector<AppCacheDatabase::GroupRecord>::const_iterator group;
112 for (group = groups.begin(); group != groups.end(); ++group) {
113 sql::Transaction transaction(connection);
114 if (!transaction.Begin()) {
115 NOTREACHED() << "Failed to start transaction";
118 std::vector<int64> deletable_response_ids;
119 bool success = DeleteGroupAndRelatedRecords(database,
121 &deletable_response_ids);
122 success = success && transaction.Commit();
130 // DatabaseTask -----------------------------------------
132 class AppCacheStorageImpl::DatabaseTask
133 : public base::RefCountedThreadSafe<DatabaseTask> {
135 explicit DatabaseTask(AppCacheStorageImpl* storage)
136 : storage_(storage), database_(storage->database_),
137 io_thread_(base::MessageLoopProxy::current()) {
138 DCHECK(io_thread_.get());
141 void AddDelegate(DelegateReference* delegate_reference) {
142 delegates_.push_back(make_scoped_refptr(delegate_reference));
145 // Schedules a task to be Run() on the DB thread. Tasks
146 // are run in the order in which they are scheduled.
149 // Called on the DB thread.
150 virtual void Run() = 0;
152 // Called on the IO thread after Run() has completed.
153 virtual void RunCompleted() {}
155 // Once scheduled a task cannot be cancelled, but the
156 // call to RunCompleted may be. This method should only be
157 // called on the IO thread. This is used by AppCacheStorageImpl
158 // to cancel the completion calls when AppCacheStorageImpl is
159 // destructed. This method may be overriden to release or delete
160 // additional data associated with the task that is not DB thread
161 // safe. If overriden, this base class method must be called from
162 // within the override.
163 virtual void CancelCompletion();
166 friend class base::RefCountedThreadSafe<DatabaseTask>;
167 virtual ~DatabaseTask() {}
169 AppCacheStorageImpl* storage_;
170 AppCacheDatabase* database_;
171 DelegateReferenceVector delegates_;
174 void CallRun(base::TimeTicks schedule_time);
175 void CallRunCompleted(base::TimeTicks schedule_time);
176 void CallDisableStorage();
178 scoped_refptr<base::MessageLoopProxy> io_thread_;
181 void AppCacheStorageImpl::DatabaseTask::Schedule() {
183 DCHECK(io_thread_->BelongsToCurrentThread());
184 if (!storage_->database_)
187 if (storage_->db_thread_->PostTask(
189 base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) {
190 storage_->scheduled_database_tasks_.push_back(this);
192 NOTREACHED() << "Thread for database tasks is not running.";
196 void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
197 DCHECK(io_thread_->BelongsToCurrentThread());
202 void AppCacheStorageImpl::DatabaseTask::CallRun(
203 base::TimeTicks schedule_time) {
204 AppCacheHistograms::AddTaskQueueTimeSample(
205 base::TimeTicks::Now() - schedule_time);
206 if (!database_->is_disabled()) {
207 base::TimeTicks run_time = base::TimeTicks::Now();
209 AppCacheHistograms::AddTaskRunTimeSample(
210 base::TimeTicks::Now() - run_time);
211 if (database_->is_disabled()) {
212 io_thread_->PostTask(
214 base::Bind(&DatabaseTask::CallDisableStorage, this));
217 io_thread_->PostTask(
219 base::Bind(&DatabaseTask::CallRunCompleted, this,
220 base::TimeTicks::Now()));
223 void AppCacheStorageImpl::DatabaseTask::CallRunCompleted(
224 base::TimeTicks schedule_time) {
225 AppCacheHistograms::AddCompletionQueueTimeSample(
226 base::TimeTicks::Now() - schedule_time);
228 DCHECK(io_thread_->BelongsToCurrentThread());
229 DCHECK(storage_->scheduled_database_tasks_.front() == this);
230 storage_->scheduled_database_tasks_.pop_front();
231 base::TimeTicks run_time = base::TimeTicks::Now();
233 AppCacheHistograms::AddCompletionRunTimeSample(
234 base::TimeTicks::Now() - run_time);
239 void AppCacheStorageImpl::DatabaseTask::CallDisableStorage() {
241 DCHECK(io_thread_->BelongsToCurrentThread());
248 class AppCacheStorageImpl::InitTask : public DatabaseTask {
250 explicit InitTask(AppCacheStorageImpl* storage)
251 : DatabaseTask(storage), last_group_id_(0),
252 last_cache_id_(0), last_response_id_(0),
253 last_deletable_response_rowid_(0) {}
256 virtual void Run() OVERRIDE;
257 virtual void RunCompleted() OVERRIDE;
260 virtual ~InitTask() {}
263 int64 last_group_id_;
264 int64 last_cache_id_;
265 int64 last_response_id_;
266 int64 last_deletable_response_rowid_;
267 std::map<GURL, int64> usage_map_;
270 void AppCacheStorageImpl::InitTask::Run() {
271 database_->FindLastStorageIds(
272 &last_group_id_, &last_cache_id_, &last_response_id_,
273 &last_deletable_response_rowid_);
274 database_->GetAllOriginUsage(&usage_map_);
277 void AppCacheStorageImpl::InitTask::RunCompleted() {
278 storage_->last_group_id_ = last_group_id_;
279 storage_->last_cache_id_ = last_cache_id_;
280 storage_->last_response_id_ = last_response_id_;
281 storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_;
283 if (!storage_->is_disabled()) {
284 storage_->usage_map_.swap(usage_map_);
285 const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
286 base::MessageLoop::current()->PostDelayedTask(
288 base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
289 storage_->weak_factory_.GetWeakPtr()),
293 if (storage_->service()->quota_client())
294 storage_->service()->quota_client()->NotifyAppCacheReady();
297 // CloseConnectionTask -------
299 class AppCacheStorageImpl::CloseConnectionTask : public DatabaseTask {
301 explicit CloseConnectionTask(AppCacheStorageImpl* storage)
302 : DatabaseTask(storage) {}
305 virtual void Run() OVERRIDE { database_->CloseConnection(); }
308 virtual ~CloseConnectionTask() {}
311 // DisableDatabaseTask -------
313 class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
315 explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
316 : DatabaseTask(storage) {}
319 virtual void Run() OVERRIDE { database_->Disable(); }
322 virtual ~DisableDatabaseTask() {}
325 // GetAllInfoTask -------
327 class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
329 explicit GetAllInfoTask(AppCacheStorageImpl* storage)
330 : DatabaseTask(storage),
331 info_collection_(new AppCacheInfoCollection()) {
335 virtual void Run() OVERRIDE;
336 virtual void RunCompleted() OVERRIDE;
339 virtual ~GetAllInfoTask() {}
342 scoped_refptr<AppCacheInfoCollection> info_collection_;
345 void AppCacheStorageImpl::GetAllInfoTask::Run() {
346 std::set<GURL> origins;
347 database_->FindOriginsWithGroups(&origins);
348 for (std::set<GURL>::const_iterator origin = origins.begin();
349 origin != origins.end(); ++origin) {
350 AppCacheInfoVector& infos =
351 info_collection_->infos_by_origin[*origin];
352 std::vector<AppCacheDatabase::GroupRecord> groups;
353 database_->FindGroupsForOrigin(*origin, &groups);
354 for (std::vector<AppCacheDatabase::GroupRecord>::const_iterator
355 group = groups.begin();
356 group != groups.end(); ++group) {
357 AppCacheDatabase::CacheRecord cache_record;
358 database_->FindCacheForGroup(group->group_id, &cache_record);
360 info.manifest_url = group->manifest_url;
361 info.creation_time = group->creation_time;
362 info.size = cache_record.cache_size;
363 info.last_access_time = group->last_access_time;
364 info.last_update_time = cache_record.update_time;
365 info.cache_id = cache_record.cache_id;
366 info.group_id = group->group_id;
367 info.is_complete = true;
368 infos.push_back(info);
373 void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() {
374 DCHECK(delegates_.size() == 1);
375 FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_.get()));
378 // StoreOrLoadTask -------
380 class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
382 explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
383 : DatabaseTask(storage) {}
384 virtual ~StoreOrLoadTask() {}
386 bool FindRelatedCacheRecords(int64 cache_id);
387 void CreateCacheAndGroupFromRecords(
388 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group);
390 AppCacheDatabase::GroupRecord group_record_;
391 AppCacheDatabase::CacheRecord cache_record_;
392 std::vector<AppCacheDatabase::EntryRecord> entry_records_;
393 std::vector<AppCacheDatabase::NamespaceRecord>
394 intercept_namespace_records_;
395 std::vector<AppCacheDatabase::NamespaceRecord>
396 fallback_namespace_records_;
397 std::vector<AppCacheDatabase::OnlineWhiteListRecord>
398 online_whitelist_records_;
401 bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
403 return database_->FindEntriesForCache(cache_id, &entry_records_) &&
404 database_->FindNamespacesForCache(
405 cache_id, &intercept_namespace_records_,
406 &fallback_namespace_records_) &&
407 database_->FindOnlineWhiteListForCache(
408 cache_id, &online_whitelist_records_);
411 void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
412 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) {
413 DCHECK(storage_ && cache && group);
415 (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id);
417 (*group) = cache->get()->owning_group();
418 DCHECK(group->get());
419 DCHECK_EQ(group_record_.group_id, group->get()->group_id());
421 // TODO(michaeln): histogram is fishing for clues to crbug/95101
422 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
423 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
424 AppCacheHistograms::CALLSITE_0);
427 storage_->NotifyStorageAccessed(group_record_.origin);
431 (*cache) = new AppCache(storage_, cache_record_.cache_id);
432 cache->get()->InitializeWithDatabaseRecords(
433 cache_record_, entry_records_,
434 intercept_namespace_records_,
435 fallback_namespace_records_,
436 online_whitelist_records_);
437 cache->get()->set_complete(true);
439 (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url);
441 DCHECK(group_record_.group_id == group->get()->group_id());
442 group->get()->AddCache(cache->get());
444 // TODO(michaeln): histogram is fishing for clues to crbug/95101
445 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
446 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
447 AppCacheHistograms::CALLSITE_1);
450 (*group) = new AppCacheGroup(
451 storage_, group_record_.manifest_url,
452 group_record_.group_id);
453 group->get()->set_creation_time(group_record_.creation_time);
454 group->get()->AddCache(cache->get());
456 // TODO(michaeln): histogram is fishing for clues to crbug/95101
457 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
458 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
459 AppCacheHistograms::CALLSITE_2);
462 DCHECK(group->get()->newest_complete_cache() == cache->get());
464 // We have to update foriegn entries if MarkEntryAsForeignTasks
466 std::vector<GURL> urls;
467 storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls);
468 for (std::vector<GURL>::iterator iter = urls.begin();
469 iter != urls.end(); ++iter) {
470 DCHECK(cache->get()->GetEntry(*iter));
471 cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN);
474 storage_->NotifyStorageAccessed(group_record_.origin);
476 // TODO(michaeln): Maybe verify that the responses we expect to exist
477 // do actually exist in the disk_cache (and if not then what?)
480 // CacheLoadTask -------
482 class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask {
484 CacheLoadTask(int64 cache_id, AppCacheStorageImpl* storage)
485 : StoreOrLoadTask(storage), cache_id_(cache_id),
489 virtual void Run() OVERRIDE;
490 virtual void RunCompleted() OVERRIDE;
493 virtual ~CacheLoadTask() {}
500 void AppCacheStorageImpl::CacheLoadTask::Run() {
502 database_->FindCache(cache_id_, &cache_record_) &&
503 database_->FindGroup(cache_record_.group_id, &group_record_) &&
504 FindRelatedCacheRecords(cache_id_);
507 database_->UpdateGroupLastAccessTime(group_record_.group_id,
511 void AppCacheStorageImpl::CacheLoadTask::RunCompleted() {
512 storage_->pending_cache_loads_.erase(cache_id_);
513 scoped_refptr<AppCache> cache;
514 scoped_refptr<AppCacheGroup> group;
515 if (success_ && !storage_->is_disabled()) {
516 DCHECK(cache_record_.cache_id == cache_id_);
517 CreateCacheAndGroupFromRecords(&cache, &group);
519 FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache.get(), cache_id_));
522 // GroupLoadTask -------
524 class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask {
526 GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
527 : StoreOrLoadTask(storage), manifest_url_(manifest_url),
531 virtual void Run() OVERRIDE;
532 virtual void RunCompleted() OVERRIDE;
535 virtual ~GroupLoadTask() {}
542 void AppCacheStorageImpl::GroupLoadTask::Run() {
544 database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
545 database_->FindCacheForGroup(group_record_.group_id, &cache_record_) &&
546 FindRelatedCacheRecords(cache_record_.cache_id);
549 database_->UpdateGroupLastAccessTime(group_record_.group_id,
553 void AppCacheStorageImpl::GroupLoadTask::RunCompleted() {
554 storage_->pending_group_loads_.erase(manifest_url_);
555 scoped_refptr<AppCacheGroup> group;
556 scoped_refptr<AppCache> cache;
557 if (!storage_->is_disabled()) {
559 DCHECK(group_record_.manifest_url == manifest_url_);
560 CreateCacheAndGroupFromRecords(&cache, &group);
562 group = storage_->working_set_.GetGroup(manifest_url_);
565 new AppCacheGroup(storage_, manifest_url_, storage_->NewGroupId());
569 FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group.get(), manifest_url_));
572 // StoreGroupAndCacheTask -------
574 class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
576 StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
577 AppCache* newest_cache);
579 void GetQuotaThenSchedule();
580 void OnQuotaCallback(
581 quota::QuotaStatusCode status, int64 usage, int64 quota);
584 virtual void Run() OVERRIDE;
585 virtual void RunCompleted() OVERRIDE;
586 virtual void CancelCompletion() OVERRIDE;
589 virtual ~StoreGroupAndCacheTask() {}
592 scoped_refptr<AppCacheGroup> group_;
593 scoped_refptr<AppCache> cache_;
595 bool would_exceed_quota_;
596 int64 space_available_;
597 int64 new_origin_usage_;
598 std::vector<int64> newly_deletable_response_ids_;
601 AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
602 AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache)
603 : StoreOrLoadTask(storage), group_(group), cache_(newest_cache),
604 success_(false), would_exceed_quota_(false),
605 space_available_(-1), new_origin_usage_(-1) {
606 group_record_.group_id = group->group_id();
607 group_record_.manifest_url = group->manifest_url();
608 group_record_.origin = group_record_.manifest_url.GetOrigin();
609 newest_cache->ToDatabaseRecords(
611 &cache_record_, &entry_records_,
612 &intercept_namespace_records_,
613 &fallback_namespace_records_,
614 &online_whitelist_records_);
617 void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
618 quota::QuotaManager* quota_manager = NULL;
619 if (storage_->service()->quota_manager_proxy()) {
621 storage_->service()->quota_manager_proxy()->quota_manager();
624 if (!quota_manager) {
625 if (storage_->service()->special_storage_policy() &&
626 storage_->service()->special_storage_policy()->IsStorageUnlimited(
627 group_record_.origin))
628 space_available_ = kint64max;
633 // We have to ask the quota manager for the value.
634 storage_->pending_quota_queries_.insert(this);
635 quota_manager->GetUsageAndQuota(
636 group_record_.origin, quota::kStorageTypeTemporary,
637 base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this));
640 void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
641 quota::QuotaStatusCode status, int64 usage, int64 quota) {
643 if (status == quota::kQuotaStatusOk)
644 space_available_ = std::max(static_cast<int64>(0), quota - usage);
646 space_available_ = 0;
647 storage_->pending_quota_queries_.erase(this);
652 void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
654 sql::Connection* connection = database_->db_connection();
658 sql::Transaction transaction(connection);
659 if (!transaction.Begin())
662 int64 old_origin_usage = database_->GetOriginUsage(group_record_.origin);
664 AppCacheDatabase::GroupRecord existing_group;
665 success_ = database_->FindGroup(group_record_.group_id, &existing_group);
667 group_record_.creation_time = base::Time::Now();
668 group_record_.last_access_time = base::Time::Now();
669 success_ = database_->InsertGroup(&group_record_);
671 DCHECK(group_record_.group_id == existing_group.group_id);
672 DCHECK(group_record_.manifest_url == existing_group.manifest_url);
673 DCHECK(group_record_.origin == existing_group.origin);
675 database_->UpdateGroupLastAccessTime(group_record_.group_id,
678 AppCacheDatabase::CacheRecord cache;
679 if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
680 // Get the set of response ids in the old cache.
681 std::set<int64> existing_response_ids;
682 database_->FindResponseIdsForCacheAsSet(cache.cache_id,
683 &existing_response_ids);
685 // Remove those that remain in the new cache.
686 std::vector<AppCacheDatabase::EntryRecord>::const_iterator entry_iter =
687 entry_records_.begin();
688 while (entry_iter != entry_records_.end()) {
689 existing_response_ids.erase(entry_iter->response_id);
693 // The rest are deletable.
694 std::set<int64>::const_iterator id_iter = existing_response_ids.begin();
695 while (id_iter != existing_response_ids.end()) {
696 newly_deletable_response_ids_.push_back(*id_iter);
701 database_->DeleteCache(cache.cache_id) &&
702 database_->DeleteEntriesForCache(cache.cache_id) &&
703 database_->DeleteNamespacesForCache(cache.cache_id) &&
704 database_->DeleteOnlineWhiteListForCache(cache.cache_id) &&
705 database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
706 // TODO(michaeln): store group_id too with deletable ids
708 NOTREACHED() << "A existing group without a cache is unexpected";
714 database_->InsertCache(&cache_record_) &&
715 database_->InsertEntryRecords(entry_records_) &&
716 database_->InsertNamespaceRecords(intercept_namespace_records_) &&
717 database_->InsertNamespaceRecords(fallback_namespace_records_) &&
718 database_->InsertOnlineWhiteListRecords(online_whitelist_records_);
723 new_origin_usage_ = database_->GetOriginUsage(group_record_.origin);
725 // Only check quota when the new usage exceeds the old usage.
726 if (new_origin_usage_ <= old_origin_usage) {
727 success_ = transaction.Commit();
731 // Use a simple hard-coded value when not using quota management.
732 if (space_available_ == -1) {
733 if (new_origin_usage_ > kDefaultQuota) {
734 would_exceed_quota_ = true;
738 success_ = transaction.Commit();
742 // Check limits based on the space availbable given to us via the
744 int64 delta = new_origin_usage_ - old_origin_usage;
745 if (delta > space_available_) {
746 would_exceed_quota_ = true;
751 success_ = transaction.Commit();
754 void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
756 storage_->UpdateUsageMapAndNotify(
757 group_->manifest_url().GetOrigin(), new_origin_usage_);
758 if (cache_.get() != group_->newest_complete_cache()) {
759 cache_->set_complete(true);
760 group_->AddCache(cache_.get());
762 if (group_->creation_time().is_null())
763 group_->set_creation_time(group_record_.creation_time);
764 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
768 OnGroupAndNewestCacheStored(
769 group_.get(), cache_.get(), success_, would_exceed_quota_));
773 // TODO(michaeln): if (would_exceed_quota_) what if the current usage
774 // also exceeds the quota? http://crbug.com/83968
777 void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
778 // Overriden to safely drop our reference to the group and cache
779 // which are not thread safe refcounted.
780 DatabaseTask::CancelCompletion();
785 // FindMainResponseTask -------
787 // Helpers for FindMainResponseTask::Run()
789 class SortByCachePreference
790 : public std::binary_function<
791 AppCacheDatabase::EntryRecord,
792 AppCacheDatabase::EntryRecord,
795 SortByCachePreference(int64 preferred_id, const std::set<int64>& in_use_ids)
796 : preferred_id_(preferred_id), in_use_ids_(in_use_ids) {
799 const AppCacheDatabase::EntryRecord& lhs,
800 const AppCacheDatabase::EntryRecord& rhs) {
801 return compute_value(lhs) > compute_value(rhs);
804 int compute_value(const AppCacheDatabase::EntryRecord& entry) {
805 if (entry.cache_id == preferred_id_)
807 else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end())
812 const std::set<int64>& in_use_ids_;
816 const AppCacheDatabase::NamespaceRecord& lhs,
817 const AppCacheDatabase::NamespaceRecord& rhs) {
818 return lhs.namespace_.namespace_url.spec().length() >
819 rhs.namespace_.namespace_url.spec().length();
822 class NetworkNamespaceHelper {
824 explicit NetworkNamespaceHelper(AppCacheDatabase* database)
825 : database_(database) {
828 bool IsInNetworkNamespace(const GURL& url, int64 cache_id) {
829 typedef std::pair<WhiteListMap::iterator, bool> InsertResult;
830 InsertResult result = namespaces_map_.insert(
831 WhiteListMap::value_type(cache_id, NamespaceVector()));
833 GetOnlineWhiteListForCache(cache_id, &result.first->second);
834 return AppCache::FindNamespace(result.first->second, url) != NULL;
838 void GetOnlineWhiteListForCache(
839 int64 cache_id, NamespaceVector* namespaces) {
840 DCHECK(namespaces && namespaces->empty());
841 typedef std::vector<AppCacheDatabase::OnlineWhiteListRecord>
843 WhiteListVector records;
844 if (!database_->FindOnlineWhiteListForCache(cache_id, &records))
846 WhiteListVector::const_iterator iter = records.begin();
847 while (iter != records.end()) {
848 namespaces->push_back(
849 Namespace(NETWORK_NAMESPACE, iter->namespace_url, GURL(),
856 typedef std::map<int64, NamespaceVector> WhiteListMap;
857 WhiteListMap namespaces_map_;
858 AppCacheDatabase* database_;
863 class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
865 FindMainResponseTask(AppCacheStorageImpl* storage,
867 const GURL& preferred_manifest_url,
868 const AppCacheWorkingSet::GroupMap* groups_in_use)
869 : DatabaseTask(storage), url_(url),
870 preferred_manifest_url_(preferred_manifest_url),
871 cache_id_(kNoCacheId), group_id_(0) {
873 for (AppCacheWorkingSet::GroupMap::const_iterator it =
874 groups_in_use->begin();
875 it != groups_in_use->end(); ++it) {
876 AppCacheGroup* group = it->second;
877 AppCache* cache = group->newest_complete_cache();
878 if (group->is_obsolete() || !cache)
880 cache_ids_in_use_.insert(cache->cache_id());
886 virtual void Run() OVERRIDE;
887 virtual void RunCompleted() OVERRIDE;
890 virtual ~FindMainResponseTask() {}
893 typedef std::vector<AppCacheDatabase::NamespaceRecord*>
894 NamespaceRecordPtrVector;
896 bool FindExactMatch(int64 preferred_id);
897 bool FindNamespaceMatch(int64 preferred_id);
898 bool FindNamespaceHelper(
899 int64 preferred_cache_id,
900 AppCacheDatabase::NamespaceRecordVector* namespaces,
901 NetworkNamespaceHelper* network_namespace_helper);
902 bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces);
905 GURL preferred_manifest_url_;
906 std::set<int64> cache_ids_in_use_;
907 AppCacheEntry entry_;
908 AppCacheEntry fallback_entry_;
909 GURL namespace_entry_url_;
917 void AppCacheStorageImpl::FindMainResponseTask::Run() {
918 // NOTE: The heuristics around choosing amoungst multiple candidates
919 // is underspecified, and just plain not fully understood. This needs
922 // The 'preferred_manifest_url' is the url of the manifest associated
923 // with the page that opened or embedded the page being loaded now.
924 // We have a strong preference to use resources from that cache.
925 // We also have a lesser bias to use resources from caches that are currently
926 // being used by other unrelated pages.
927 // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases
928 // - when navigating a frame whose current contents are from an appcache
929 // - when clicking an href in a frame that is appcached
930 int64 preferred_cache_id = kNoCacheId;
931 if (!preferred_manifest_url_.is_empty()) {
932 AppCacheDatabase::GroupRecord preferred_group;
933 AppCacheDatabase::CacheRecord preferred_cache;
934 if (database_->FindGroupForManifestUrl(
935 preferred_manifest_url_, &preferred_group) &&
936 database_->FindCacheForGroup(
937 preferred_group.group_id, &preferred_cache)) {
938 preferred_cache_id = preferred_cache.cache_id;
942 if (FindExactMatch(preferred_cache_id) ||
943 FindNamespaceMatch(preferred_cache_id)) {
944 // We found something.
945 DCHECK(cache_id_ != kNoCacheId && !manifest_url_.is_empty() &&
950 // We didn't find anything.
951 DCHECK(cache_id_ == kNoCacheId && manifest_url_.is_empty() &&
955 bool AppCacheStorageImpl::
956 FindMainResponseTask::FindExactMatch(int64 preferred_cache_id) {
957 std::vector<AppCacheDatabase::EntryRecord> entries;
958 if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
959 // Sort them in order of preference, from the preferred_cache first,
960 // followed by hits from caches that are 'in use', then the rest.
961 std::sort(entries.begin(), entries.end(),
962 SortByCachePreference(preferred_cache_id, cache_ids_in_use_));
964 // Take the first with a valid, non-foreign entry.
965 std::vector<AppCacheDatabase::EntryRecord>::iterator iter;
966 for (iter = entries.begin(); iter < entries.end(); ++iter) {
967 AppCacheDatabase::GroupRecord group_record;
968 if ((iter->flags & AppCacheEntry::FOREIGN) ||
969 !database_->FindGroupForCache(iter->cache_id, &group_record)) {
972 manifest_url_ = group_record.manifest_url;
973 group_id_ = group_record.group_id;
974 entry_ = AppCacheEntry(iter->flags, iter->response_id);
975 cache_id_ = iter->cache_id;
976 return true; // We found an exact match.
982 bool AppCacheStorageImpl::
983 FindMainResponseTask::FindNamespaceMatch(int64 preferred_cache_id) {
984 AppCacheDatabase::NamespaceRecordVector all_intercepts;
985 AppCacheDatabase::NamespaceRecordVector all_fallbacks;
986 if (!database_->FindNamespacesForOrigin(
987 url_.GetOrigin(), &all_intercepts, &all_fallbacks)
988 || (all_intercepts.empty() && all_fallbacks.empty())) {
992 NetworkNamespaceHelper network_namespace_helper(database_);
993 if (FindNamespaceHelper(preferred_cache_id,
995 &network_namespace_helper) ||
996 FindNamespaceHelper(preferred_cache_id,
998 &network_namespace_helper)) {
1004 bool AppCacheStorageImpl::
1005 FindMainResponseTask::FindNamespaceHelper(
1006 int64 preferred_cache_id,
1007 AppCacheDatabase::NamespaceRecordVector* namespaces,
1008 NetworkNamespaceHelper* network_namespace_helper) {
1009 // Sort them by length, longer matches within the same cache/bucket take
1011 std::sort(namespaces->begin(), namespaces->end(), SortByLength);
1013 NamespaceRecordPtrVector preferred_namespaces;
1014 NamespaceRecordPtrVector inuse_namespaces;
1015 NamespaceRecordPtrVector other_namespaces;
1016 std::vector<AppCacheDatabase::NamespaceRecord>::iterator iter;
1017 for (iter = namespaces->begin(); iter < namespaces->end(); ++iter) {
1018 // Skip those that aren't a match.
1019 if (!iter->namespace_.IsMatch(url_))
1022 // Skip namespaces where the requested url falls into a network
1023 // namespace of its containing appcache.
1024 if (network_namespace_helper->IsInNetworkNamespace(url_, iter->cache_id))
1027 // Bin them into one of our three buckets.
1028 if (iter->cache_id == preferred_cache_id)
1029 preferred_namespaces.push_back(&(*iter));
1030 else if (cache_ids_in_use_.find(iter->cache_id) != cache_ids_in_use_.end())
1031 inuse_namespaces.push_back(&(*iter));
1033 other_namespaces.push_back(&(*iter));
1036 if (FindFirstValidNamespace(preferred_namespaces) ||
1037 FindFirstValidNamespace(inuse_namespaces) ||
1038 FindFirstValidNamespace(other_namespaces))
1039 return true; // We found one.
1041 // We didn't find anything.
1045 bool AppCacheStorageImpl::
1046 FindMainResponseTask::FindFirstValidNamespace(
1047 const NamespaceRecordPtrVector& namespaces) {
1048 // Take the first with a valid, non-foreign entry.
1049 NamespaceRecordPtrVector::const_iterator iter;
1050 for (iter = namespaces.begin(); iter < namespaces.end(); ++iter) {
1051 AppCacheDatabase::EntryRecord entry_record;
1052 if (database_->FindEntry((*iter)->cache_id, (*iter)->namespace_.target_url,
1054 AppCacheDatabase::GroupRecord group_record;
1055 if ((entry_record.flags & AppCacheEntry::FOREIGN) ||
1056 !database_->FindGroupForCache(entry_record.cache_id, &group_record)) {
1059 manifest_url_ = group_record.manifest_url;
1060 group_id_ = group_record.group_id;
1061 cache_id_ = (*iter)->cache_id;
1062 namespace_entry_url_ = (*iter)->namespace_.target_url;
1063 if ((*iter)->namespace_.type == FALLBACK_NAMESPACE)
1064 fallback_entry_ = AppCacheEntry(entry_record.flags,
1065 entry_record.response_id);
1067 entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id);
1068 return true; // We found one.
1071 return false; // We didn't find a match.
1074 void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
1075 storage_->CallOnMainResponseFound(
1076 &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_,
1077 cache_id_, group_id_, manifest_url_);
1080 // MarkEntryAsForeignTask -------
1082 class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask {
1084 MarkEntryAsForeignTask(
1085 AppCacheStorageImpl* storage, const GURL& url, int64 cache_id)
1086 : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}
1089 virtual void Run() OVERRIDE;
1090 virtual void RunCompleted() OVERRIDE;
1093 virtual ~MarkEntryAsForeignTask() {}
1100 void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() {
1101 database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN);
1104 void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() {
1105 DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ &&
1106 storage_->pending_foreign_markings_.front().second == cache_id_);
1107 storage_->pending_foreign_markings_.pop_front();
1110 // MakeGroupObsoleteTask -------
1112 class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
1114 MakeGroupObsoleteTask(AppCacheStorageImpl* storage, AppCacheGroup* group);
1117 virtual void Run() OVERRIDE;
1118 virtual void RunCompleted() OVERRIDE;
1119 virtual void CancelCompletion() OVERRIDE;
1122 virtual ~MakeGroupObsoleteTask() {}
1125 scoped_refptr<AppCacheGroup> group_;
1129 int64 new_origin_usage_;
1130 std::vector<int64> newly_deletable_response_ids_;
1133 AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
1134 AppCacheStorageImpl* storage, AppCacheGroup* group)
1135 : DatabaseTask(storage), group_(group), group_id_(group->group_id()),
1136 origin_(group->manifest_url().GetOrigin()),
1137 success_(false), new_origin_usage_(-1) {
1140 void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
1142 sql::Connection* connection = database_->db_connection();
1146 sql::Transaction transaction(connection);
1147 if (!transaction.Begin())
1150 AppCacheDatabase::GroupRecord group_record;
1151 if (!database_->FindGroup(group_id_, &group_record)) {
1152 // This group doesn't exists in the database, nothing todo here.
1153 new_origin_usage_ = database_->GetOriginUsage(origin_);
1158 DCHECK_EQ(group_record.origin, origin_);
1159 success_ = DeleteGroupAndRelatedRecords(database_,
1161 &newly_deletable_response_ids_);
1163 new_origin_usage_ = database_->GetOriginUsage(origin_);
1164 success_ = success_ && transaction.Commit();
1167 void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
1169 group_->set_obsolete(true);
1170 if (!storage_->is_disabled()) {
1171 storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_);
1172 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
1174 // Also remove from the working set, caches for an 'obsolete' group
1175 // may linger in use, but the group itself cannot be looked up by
1176 // 'manifest_url' in the working set any longer.
1177 storage_->working_set()->RemoveGroup(group_.get());
1180 FOR_EACH_DELEGATE(delegates_, OnGroupMadeObsolete(group_.get(), success_));
1184 void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() {
1185 // Overriden to safely drop our reference to the group
1186 // which is not thread safe refcounted.
1187 DatabaseTask::CancelCompletion();
1191 // GetDeletableResponseIdsTask -------
1193 class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask {
1195 GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64 max_rowid)
1196 : DatabaseTask(storage), max_rowid_(max_rowid) {}
1199 virtual void Run() OVERRIDE;
1200 virtual void RunCompleted() OVERRIDE;
1203 virtual ~GetDeletableResponseIdsTask() {}
1207 std::vector<int64> response_ids_;
1210 void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() {
1211 const int kSqlLimit = 1000;
1212 database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit);
1213 // TODO(michaeln): retrieve group_ids too
1216 void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() {
1217 if (!response_ids_.empty())
1218 storage_->StartDeletingResponses(response_ids_);
1221 // InsertDeletableResponseIdsTask -------
1223 class AppCacheStorageImpl::InsertDeletableResponseIdsTask
1224 : public DatabaseTask {
1226 explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1227 : DatabaseTask(storage) {}
1230 virtual void Run() OVERRIDE;
1232 std::vector<int64> response_ids_;
1235 virtual ~InsertDeletableResponseIdsTask() {}
1238 void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() {
1239 database_->InsertDeletableResponseIds(response_ids_);
1240 // TODO(michaeln): store group_ids too
1243 // DeleteDeletableResponseIdsTask -------
1245 class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
1246 : public DatabaseTask {
1248 explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1249 : DatabaseTask(storage) {}
1252 virtual void Run() OVERRIDE;
1254 std::vector<int64> response_ids_;
1257 virtual ~DeleteDeletableResponseIdsTask() {}
1260 void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
1261 database_->DeleteDeletableResponseIds(response_ids_);
1264 // UpdateGroupLastAccessTimeTask -------
1266 class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask
1267 : public DatabaseTask {
1269 UpdateGroupLastAccessTimeTask(
1270 AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time)
1271 : DatabaseTask(storage), group_id_(group->group_id()),
1272 last_access_time_(time) {
1273 storage->NotifyStorageAccessed(group->manifest_url().GetOrigin());
1277 virtual void Run() OVERRIDE;
1280 virtual ~UpdateGroupLastAccessTimeTask() {}
1284 base::Time last_access_time_;
1287 void AppCacheStorageImpl::UpdateGroupLastAccessTimeTask::Run() {
1288 database_->UpdateGroupLastAccessTime(group_id_, last_access_time_);
1292 // AppCacheStorageImpl ---------------------------------------------------
1294 AppCacheStorageImpl::AppCacheStorageImpl(AppCacheService* service)
1295 : AppCacheStorage(service),
1296 is_incognito_(false),
1297 is_response_deletion_scheduled_(false),
1298 did_start_deleting_responses_(false),
1299 last_deletable_response_rowid_(0),
1301 is_disabled_(false),
1302 weak_factory_(this) {
1305 AppCacheStorageImpl::~AppCacheStorageImpl() {
1306 std::for_each(pending_quota_queries_.begin(),
1307 pending_quota_queries_.end(),
1308 std::mem_fun(&DatabaseTask::CancelCompletion));
1309 std::for_each(scheduled_database_tasks_.begin(),
1310 scheduled_database_tasks_.end(),
1311 std::mem_fun(&DatabaseTask::CancelCompletion));
1314 !db_thread_->PostTask(
1316 base::Bind(&ClearSessionOnlyOrigins, database_,
1317 make_scoped_refptr(service_->special_storage_policy()),
1318 service()->force_keep_session_state()))) {
1321 database_ = NULL; // So no further database tasks can be scheduled.
1324 void AppCacheStorageImpl::Initialize(const base::FilePath& cache_directory,
1325 base::MessageLoopProxy* db_thread,
1326 base::MessageLoopProxy* cache_thread) {
1329 cache_directory_ = cache_directory;
1330 is_incognito_ = cache_directory_.empty();
1332 base::FilePath db_file_path;
1334 db_file_path = cache_directory_.Append(kAppCacheDatabaseName);
1335 database_ = new AppCacheDatabase(db_file_path);
1337 db_thread_ = db_thread;
1338 cache_thread_ = cache_thread;
1340 scoped_refptr<InitTask> task(new InitTask(this));
1344 void AppCacheStorageImpl::Disable() {
1347 VLOG(1) << "Disabling appcache storage.";
1348 is_disabled_ = true;
1349 ClearUsageMapAndNotify();
1350 working_set()->Disable();
1352 disk_cache_->Disable();
1353 scoped_refptr<DisableDatabaseTask> task(new DisableDatabaseTask(this));
1357 void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) {
1359 scoped_refptr<GetAllInfoTask> task(new GetAllInfoTask(this));
1360 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1364 void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) {
1367 delegate->OnCacheLoaded(NULL, id);
1371 AppCache* cache = working_set_.GetCache(id);
1373 delegate->OnCacheLoaded(cache, id);
1374 if (cache->owning_group()) {
1375 scoped_refptr<DatabaseTask> update_task(
1376 new UpdateGroupLastAccessTimeTask(
1377 this, cache->owning_group(), base::Time::Now()));
1378 update_task->Schedule();
1382 scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id));
1384 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1387 task = new CacheLoadTask(id, this);
1388 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1390 pending_cache_loads_[id] = task.get();
1393 void AppCacheStorageImpl::LoadOrCreateGroup(
1394 const GURL& manifest_url, Delegate* delegate) {
1397 delegate->OnGroupLoaded(NULL, manifest_url);
1401 AppCacheGroup* group = working_set_.GetGroup(manifest_url);
1403 delegate->OnGroupLoaded(group, manifest_url);
1404 scoped_refptr<DatabaseTask> update_task(
1405 new UpdateGroupLastAccessTimeTask(
1406 this, group, base::Time::Now()));
1407 update_task->Schedule();
1411 scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url));
1413 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1417 if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) {
1418 // No need to query the database, return a new group immediately.
1419 scoped_refptr<AppCacheGroup> group(new AppCacheGroup(
1420 this, manifest_url, NewGroupId()));
1421 delegate->OnGroupLoaded(group.get(), manifest_url);
1425 task = new GroupLoadTask(manifest_url, this);
1426 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1428 pending_group_loads_[manifest_url] = task.get();
1431 void AppCacheStorageImpl::StoreGroupAndNewestCache(
1432 AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
1433 // TODO(michaeln): distinguish between a simple update of an existing
1434 // cache that just adds new master entry(s), and the insertion of a
1435 // whole new cache. The StoreGroupAndCacheTask as written will handle
1436 // the simple update case in a very heavy weight way (delete all and
1437 // the reinsert all over again).
1438 DCHECK(group && delegate && newest_cache);
1439 scoped_refptr<StoreGroupAndCacheTask> task(
1440 new StoreGroupAndCacheTask(this, group, newest_cache));
1441 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1442 task->GetQuotaThenSchedule();
1444 // TODO(michaeln): histogram is fishing for clues to crbug/95101
1445 if (!newest_cache->GetEntry(group->manifest_url())) {
1446 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
1447 AppCacheHistograms::CALLSITE_3);
1451 void AppCacheStorageImpl::FindResponseForMainRequest(
1452 const GURL& url, const GURL& preferred_manifest_url,
1453 Delegate* delegate) {
1456 const GURL* url_ptr = &url;
1458 if (url.has_ref()) {
1459 GURL::Replacements replacements;
1460 replacements.ClearRef();
1461 url_no_ref = url.ReplaceComponents(replacements);
1462 url_ptr = &url_no_ref;
1465 const GURL origin = url.GetOrigin();
1467 // First look in our working set for a direct hit without having to query
1469 const AppCacheWorkingSet::GroupMap* groups_in_use =
1470 working_set()->GetGroupsInOrigin(origin);
1471 if (groups_in_use) {
1472 if (!preferred_manifest_url.is_empty()) {
1473 AppCacheWorkingSet::GroupMap::const_iterator found =
1474 groups_in_use->find(preferred_manifest_url);
1475 if (found != groups_in_use->end() &&
1476 FindResponseForMainRequestInGroup(
1477 found->second, *url_ptr, delegate)) {
1481 for (AppCacheWorkingSet::GroupMap::const_iterator it =
1482 groups_in_use->begin();
1483 it != groups_in_use->end(); ++it) {
1484 if (FindResponseForMainRequestInGroup(
1485 it->second, *url_ptr, delegate)) {
1492 if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) {
1493 // No need to query the database, return async'ly but without going thru
1495 scoped_refptr<AppCacheGroup> no_group;
1496 scoped_refptr<AppCache> no_cache;
1498 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1499 weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group,
1501 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
1505 // We have to query the database, schedule a database task to do so.
1506 scoped_refptr<FindMainResponseTask> task(
1507 new FindMainResponseTask(this, *url_ptr, preferred_manifest_url,
1509 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1513 bool AppCacheStorageImpl::FindResponseForMainRequestInGroup(
1514 AppCacheGroup* group, const GURL& url, Delegate* delegate) {
1515 AppCache* cache = group->newest_complete_cache();
1516 if (group->is_obsolete() || !cache)
1519 AppCacheEntry* entry = cache->GetEntry(url);
1520 if (!entry || entry->IsForeign())
1524 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1525 weak_factory_.GetWeakPtr(), url, *entry,
1526 make_scoped_refptr(group), make_scoped_refptr(cache),
1527 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
1531 void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
1533 const AppCacheEntry& found_entry,
1534 scoped_refptr<AppCacheGroup> group,
1535 scoped_refptr<AppCache> cache,
1536 scoped_refptr<DelegateReference> delegate_ref) {
1537 if (delegate_ref->delegate) {
1538 DelegateReferenceVector delegates(1, delegate_ref);
1539 CallOnMainResponseFound(
1540 &delegates, url, found_entry,
1541 GURL(), AppCacheEntry(),
1542 cache.get() ? cache->cache_id() : kNoCacheId,
1543 group.get() ? group->group_id() : kNoCacheId,
1544 group.get() ? group->manifest_url() : GURL());
1548 void AppCacheStorageImpl::CallOnMainResponseFound(
1549 DelegateReferenceVector* delegates,
1550 const GURL& url, const AppCacheEntry& entry,
1551 const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry,
1552 int64 cache_id, int64 group_id, const GURL& manifest_url) {
1555 OnMainResponseFound(url, entry,
1556 namespace_entry_url, fallback_entry,
1557 cache_id, group_id, manifest_url));
1560 void AppCacheStorageImpl::FindResponseForSubRequest(
1561 AppCache* cache, const GURL& url,
1562 AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
1563 bool* found_network_namespace) {
1564 DCHECK(cache && cache->is_complete());
1566 // When a group is forcibly deleted, all subresource loads for pages
1567 // using caches in the group will result in a synthesized network errors.
1568 // Forcible deletion is not a function that is covered by the HTML5 spec.
1569 if (cache->owning_group()->is_being_deleted()) {
1570 *found_entry = AppCacheEntry();
1571 *found_fallback_entry = AppCacheEntry();
1572 *found_network_namespace = false;
1576 GURL fallback_namespace_not_used;
1577 GURL intercept_namespace_not_used;
1578 cache->FindResponseForRequest(
1579 url, found_entry, &intercept_namespace_not_used,
1580 found_fallback_entry, &fallback_namespace_not_used,
1581 found_network_namespace);
1584 void AppCacheStorageImpl::MarkEntryAsForeign(
1585 const GURL& entry_url, int64 cache_id) {
1586 AppCache* cache = working_set_.GetCache(cache_id);
1588 AppCacheEntry* entry = cache->GetEntry(entry_url);
1591 entry->add_types(AppCacheEntry::FOREIGN);
1593 scoped_refptr<MarkEntryAsForeignTask> task(
1594 new MarkEntryAsForeignTask(this, entry_url, cache_id));
1596 pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
1599 void AppCacheStorageImpl::MakeGroupObsolete(
1600 AppCacheGroup* group, Delegate* delegate) {
1601 DCHECK(group && delegate);
1602 scoped_refptr<MakeGroupObsoleteTask> task(
1603 new MakeGroupObsoleteTask(this, group));
1604 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1608 AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader(
1609 const GURL& manifest_url, int64 group_id, int64 response_id) {
1610 return new AppCacheResponseReader(response_id, group_id, disk_cache());
1613 AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
1614 const GURL& manifest_url, int64 group_id) {
1615 return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache());
1618 void AppCacheStorageImpl::DoomResponses(
1619 const GURL& manifest_url, const std::vector<int64>& response_ids) {
1620 if (response_ids.empty())
1623 // Start deleting them from the disk cache lazily.
1624 StartDeletingResponses(response_ids);
1626 // Also schedule a database task to record these ids in the
1627 // deletable responses table.
1628 // TODO(michaeln): There is a race here. If the browser crashes
1629 // prior to committing these rows to the database and prior to us
1630 // having deleted them from the disk cache, we'll never delete them.
1631 scoped_refptr<InsertDeletableResponseIdsTask> task(
1632 new InsertDeletableResponseIdsTask(this));
1633 task->response_ids_ = response_ids;
1637 void AppCacheStorageImpl::DeleteResponses(
1638 const GURL& manifest_url, const std::vector<int64>& response_ids) {
1639 if (response_ids.empty())
1641 StartDeletingResponses(response_ids);
1644 void AppCacheStorageImpl::PurgeMemory() {
1645 scoped_refptr<CloseConnectionTask> task(new CloseConnectionTask(this));
1649 void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() {
1650 // Only if we haven't already begun.
1651 if (!did_start_deleting_responses_) {
1652 scoped_refptr<GetDeletableResponseIdsTask> task(
1653 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
1658 void AppCacheStorageImpl::StartDeletingResponses(
1659 const std::vector<int64>& response_ids) {
1660 DCHECK(!response_ids.empty());
1661 did_start_deleting_responses_ = true;
1662 deletable_response_ids_.insert(
1663 deletable_response_ids_.end(),
1664 response_ids.begin(), response_ids.end());
1665 if (!is_response_deletion_scheduled_)
1666 ScheduleDeleteOneResponse();
1669 void AppCacheStorageImpl::ScheduleDeleteOneResponse() {
1670 DCHECK(!is_response_deletion_scheduled_);
1671 const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(10);
1672 base::MessageLoop::current()->PostDelayedTask(
1674 base::Bind(&AppCacheStorageImpl::DeleteOneResponse,
1675 weak_factory_.GetWeakPtr()),
1677 is_response_deletion_scheduled_ = true;
1680 void AppCacheStorageImpl::DeleteOneResponse() {
1681 DCHECK(is_response_deletion_scheduled_);
1682 DCHECK(!deletable_response_ids_.empty());
1684 if (!disk_cache()) {
1685 DCHECK(is_disabled_);
1686 deletable_response_ids_.clear();
1687 deleted_response_ids_.clear();
1688 is_response_deletion_scheduled_ = false;
1692 // TODO(michaeln): add group_id to DoomEntry args
1693 int64 id = deletable_response_ids_.front();
1694 int rv = disk_cache_->DoomEntry(
1695 id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse,
1696 base::Unretained(this)));
1697 if (rv != net::ERR_IO_PENDING)
1698 OnDeletedOneResponse(rv);
1701 void AppCacheStorageImpl::OnDeletedOneResponse(int rv) {
1702 is_response_deletion_scheduled_ = false;
1706 int64 id = deletable_response_ids_.front();
1707 deletable_response_ids_.pop_front();
1708 if (rv != net::ERR_ABORTED)
1709 deleted_response_ids_.push_back(id);
1711 const size_t kBatchSize = 50U;
1712 if (deleted_response_ids_.size() >= kBatchSize ||
1713 deletable_response_ids_.empty()) {
1714 scoped_refptr<DeleteDeletableResponseIdsTask> task(
1715 new DeleteDeletableResponseIdsTask(this));
1716 task->response_ids_.swap(deleted_response_ids_);
1720 if (deletable_response_ids_.empty()) {
1721 scoped_refptr<GetDeletableResponseIdsTask> task(
1722 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
1727 ScheduleDeleteOneResponse();
1730 AppCacheStorageImpl::CacheLoadTask*
1731 AppCacheStorageImpl::GetPendingCacheLoadTask(int64 cache_id) {
1732 PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id);
1733 if (found != pending_cache_loads_.end())
1734 return found->second;
1738 AppCacheStorageImpl::GroupLoadTask*
1739 AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) {
1740 PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url);
1741 if (found != pending_group_loads_.end())
1742 return found->second;
1746 void AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
1747 int64 cache_id, std::vector<GURL>* urls) {
1748 PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin();
1749 while (iter != pending_foreign_markings_.end()) {
1750 if (iter->second == cache_id)
1751 urls->push_back(iter->first);
1756 void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure& task) {
1757 pending_simple_tasks_.push_back(task);
1758 base::MessageLoop::current()->PostTask(
1760 base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask,
1761 weak_factory_.GetWeakPtr()));
1764 void AppCacheStorageImpl::RunOnePendingSimpleTask() {
1765 DCHECK(!pending_simple_tasks_.empty());
1766 base::Closure task = pending_simple_tasks_.front();
1767 pending_simple_tasks_.pop_front();
1771 AppCacheDiskCache* AppCacheStorageImpl::disk_cache() {
1772 DCHECK(IsInitTaskComplete());
1779 disk_cache_.reset(new AppCacheDiskCache);
1780 if (is_incognito_) {
1781 rv = disk_cache_->InitWithMemBackend(
1782 kMaxMemDiskCacheSize,
1783 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
1784 base::Unretained(this)));
1786 rv = disk_cache_->InitWithDiskBackend(
1787 cache_directory_.Append(kDiskCacheDirectoryName),
1790 cache_thread_.get(),
1791 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
1792 base::Unretained(this)));
1795 // We should not keep this reference around.
1796 cache_thread_ = NULL;
1798 if (rv != net::ERR_IO_PENDING)
1799 OnDiskCacheInitialized(rv);
1801 return disk_cache_.get();
1804 void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) {
1805 if (rv != net::OK) {
1806 LOG(ERROR) << "Failed to open the appcache diskcache.";
1807 AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR);
1809 // We're unable to open the disk cache, this is a fatal error that we can't
1810 // really recover from. We handle it by temporarily disabling the appcache
1811 // deleting the directory on disk and reinitializing the appcache system.
1813 if (!is_incognito_ && rv != net::ERR_ABORTED) {
1814 VLOG(1) << "Deleting existing appcache data and starting over.";
1815 db_thread_->PostTaskAndReply(
1817 base::Bind(base::IgnoreResult(&base::DeleteFile),
1818 cache_directory_, true),
1819 base::Bind(&AppCacheStorageImpl::CallReinitialize,
1820 weak_factory_.GetWeakPtr()));
1825 void AppCacheStorageImpl::CallReinitialize() {
1826 service_->Reinitialize();
1827 // note: 'this' may be deleted during reinit.
1830 } // namespace appcache