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 "content/browser/appcache/appcache_storage_impl.h"
12 #include "base/bind.h"
13 #include "base/bind_helpers.h"
14 #include "base/files/file_util.h"
15 #include "base/logging.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/stl_util.h"
19 #include "base/strings/string_util.h"
20 #include "content/browser/appcache/appcache.h"
21 #include "content/browser/appcache/appcache_database.h"
22 #include "content/browser/appcache/appcache_entry.h"
23 #include "content/browser/appcache/appcache_group.h"
24 #include "content/browser/appcache/appcache_histograms.h"
25 #include "content/browser/appcache/appcache_quota_client.h"
26 #include "content/browser/appcache/appcache_response.h"
27 #include "content/browser/appcache/appcache_service_impl.h"
28 #include "net/base/cache_type.h"
29 #include "net/base/net_errors.h"
30 #include "sql/connection.h"
31 #include "sql/transaction.h"
32 #include "storage/browser/quota/quota_client.h"
33 #include "storage/browser/quota/quota_manager.h"
34 #include "storage/browser/quota/quota_manager_proxy.h"
35 #include "storage/browser/quota/special_storage_policy.h"
39 // Hard coded default when not using quota management.
40 static const int kDefaultQuota
= 5 * 1024 * 1024;
42 static const int kMaxDiskCacheSize
= 250 * 1024 * 1024;
43 static const int kMaxMemDiskCacheSize
= 10 * 1024 * 1024;
44 static const base::FilePath::CharType kDiskCacheDirectoryName
[] =
45 FILE_PATH_LITERAL("Cache");
49 // Helpers for clearing data from the AppCacheDatabase.
50 bool DeleteGroupAndRelatedRecords(AppCacheDatabase
* database
,
52 std::vector
<int64
>* deletable_response_ids
) {
53 AppCacheDatabase::CacheRecord cache_record
;
55 if (database
->FindCacheForGroup(group_id
, &cache_record
)) {
56 database
->FindResponseIdsForCacheAsVector(cache_record
.cache_id
,
57 deletable_response_ids
);
59 database
->DeleteGroup(group_id
) &&
60 database
->DeleteCache(cache_record
.cache_id
) &&
61 database
->DeleteEntriesForCache(cache_record
.cache_id
) &&
62 database
->DeleteNamespacesForCache(cache_record
.cache_id
) &&
63 database
->DeleteOnlineWhiteListForCache(cache_record
.cache_id
) &&
64 database
->InsertDeletableResponseIds(*deletable_response_ids
);
66 NOTREACHED() << "A existing group without a cache is unexpected";
67 success
= database
->DeleteGroup(group_id
);
72 // Destroys |database|. If there is appcache data to be deleted
73 // (|force_keep_session_state| is false), deletes session-only appcache data.
74 void ClearSessionOnlyOrigins(
75 AppCacheDatabase
* database
,
76 scoped_refptr
<storage::SpecialStoragePolicy
> special_storage_policy
,
77 bool force_keep_session_state
) {
78 scoped_ptr
<AppCacheDatabase
> database_to_delete(database
);
80 // If saving session state, only delete the database.
81 if (force_keep_session_state
)
84 bool has_session_only_appcaches
=
85 special_storage_policy
.get() &&
86 special_storage_policy
->HasSessionOnlyOrigins();
88 // Clearning only session-only databases, and there are none.
89 if (!has_session_only_appcaches
)
92 std::set
<GURL
> origins
;
93 database
->FindOriginsWithGroups(&origins
);
95 return; // nothing to delete
97 sql::Connection
* connection
= database
->db_connection();
99 NOTREACHED() << "Missing database connection.";
103 std::set
<GURL
>::const_iterator origin
;
104 DCHECK(special_storage_policy
.get());
105 for (origin
= origins
.begin(); origin
!= origins
.end(); ++origin
) {
106 if (!special_storage_policy
->IsStorageSessionOnly(*origin
))
108 if (special_storage_policy
->IsStorageProtected(*origin
))
111 std::vector
<AppCacheDatabase::GroupRecord
> groups
;
112 database
->FindGroupsForOrigin(*origin
, &groups
);
113 std::vector
<AppCacheDatabase::GroupRecord
>::const_iterator group
;
114 for (group
= groups
.begin(); group
!= groups
.end(); ++group
) {
115 sql::Transaction
transaction(connection
);
116 if (!transaction
.Begin()) {
117 NOTREACHED() << "Failed to start transaction";
120 std::vector
<int64
> deletable_response_ids
;
121 bool success
= DeleteGroupAndRelatedRecords(database
,
123 &deletable_response_ids
);
124 success
= success
&& transaction
.Commit();
132 // DatabaseTask -----------------------------------------
134 class AppCacheStorageImpl::DatabaseTask
135 : public base::RefCountedThreadSafe
<DatabaseTask
> {
137 explicit DatabaseTask(AppCacheStorageImpl
* storage
)
138 : storage_(storage
), database_(storage
->database_
),
139 io_thread_(base::MessageLoopProxy::current()) {
140 DCHECK(io_thread_
.get());
143 void AddDelegate(DelegateReference
* delegate_reference
) {
144 delegates_
.push_back(make_scoped_refptr(delegate_reference
));
147 // Schedules a task to be Run() on the DB thread. Tasks
148 // are run in the order in which they are scheduled.
151 // Called on the DB thread.
152 virtual void Run() = 0;
154 // Called on the IO thread after Run() has completed.
155 virtual void RunCompleted() {}
157 // Once scheduled a task cannot be cancelled, but the
158 // call to RunCompleted may be. This method should only be
159 // called on the IO thread. This is used by AppCacheStorageImpl
160 // to cancel the completion calls when AppCacheStorageImpl is
161 // destructed. This method may be overriden to release or delete
162 // additional data associated with the task that is not DB thread
163 // safe. If overriden, this base class method must be called from
164 // within the override.
165 virtual void CancelCompletion();
168 friend class base::RefCountedThreadSafe
<DatabaseTask
>;
169 virtual ~DatabaseTask() {}
171 AppCacheStorageImpl
* storage_
;
172 AppCacheDatabase
* database_
;
173 DelegateReferenceVector delegates_
;
176 void CallRun(base::TimeTicks schedule_time
);
177 void CallRunCompleted(base::TimeTicks schedule_time
);
180 scoped_refptr
<base::MessageLoopProxy
> io_thread_
;
183 void AppCacheStorageImpl::DatabaseTask::Schedule() {
185 DCHECK(io_thread_
->BelongsToCurrentThread());
186 if (!storage_
->database_
)
189 if (storage_
->db_thread_
->PostTask(
191 base::Bind(&DatabaseTask::CallRun
, this, base::TimeTicks::Now()))) {
192 storage_
->scheduled_database_tasks_
.push_back(this);
194 NOTREACHED() << "Thread for database tasks is not running.";
198 void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
199 DCHECK(io_thread_
->BelongsToCurrentThread());
204 void AppCacheStorageImpl::DatabaseTask::CallRun(
205 base::TimeTicks schedule_time
) {
206 AppCacheHistograms::AddTaskQueueTimeSample(
207 base::TimeTicks::Now() - schedule_time
);
208 if (!database_
->is_disabled()) {
209 base::TimeTicks run_time
= base::TimeTicks::Now();
211 AppCacheHistograms::AddTaskRunTimeSample(
212 base::TimeTicks::Now() - run_time
);
214 if (database_
->was_corruption_detected()) {
215 AppCacheHistograms::CountCorruptionDetected();
216 database_
->Disable();
218 if (database_
->is_disabled()) {
219 io_thread_
->PostTask(
221 base::Bind(&DatabaseTask::OnFatalError
, this));
224 io_thread_
->PostTask(
226 base::Bind(&DatabaseTask::CallRunCompleted
, this,
227 base::TimeTicks::Now()));
230 void AppCacheStorageImpl::DatabaseTask::CallRunCompleted(
231 base::TimeTicks schedule_time
) {
232 AppCacheHistograms::AddCompletionQueueTimeSample(
233 base::TimeTicks::Now() - schedule_time
);
235 DCHECK(io_thread_
->BelongsToCurrentThread());
236 DCHECK(storage_
->scheduled_database_tasks_
.front() == this);
237 storage_
->scheduled_database_tasks_
.pop_front();
238 base::TimeTicks run_time
= base::TimeTicks::Now();
240 AppCacheHistograms::AddCompletionRunTimeSample(
241 base::TimeTicks::Now() - run_time
);
246 void AppCacheStorageImpl::DatabaseTask::OnFatalError() {
248 DCHECK(io_thread_
->BelongsToCurrentThread());
250 storage_
->DeleteAndStartOver();
256 class AppCacheStorageImpl::InitTask
: public DatabaseTask
{
258 explicit InitTask(AppCacheStorageImpl
* storage
)
259 : DatabaseTask(storage
), last_group_id_(0),
260 last_cache_id_(0), last_response_id_(0),
261 last_deletable_response_rowid_(0) {
262 if (!storage
->is_incognito_
) {
264 storage
->cache_directory_
.Append(kAppCacheDatabaseName
);
265 disk_cache_directory_
=
266 storage
->cache_directory_
.Append(kDiskCacheDirectoryName
);
272 void RunCompleted() override
;
275 ~InitTask() override
{}
278 base::FilePath db_file_path_
;
279 base::FilePath disk_cache_directory_
;
280 int64 last_group_id_
;
281 int64 last_cache_id_
;
282 int64 last_response_id_
;
283 int64 last_deletable_response_rowid_
;
284 std::map
<GURL
, int64
> usage_map_
;
287 void AppCacheStorageImpl::InitTask::Run() {
288 // If there is no sql database, ensure there is no disk cache either.
289 if (!db_file_path_
.empty() &&
290 !base::PathExists(db_file_path_
) &&
291 base::DirectoryExists(disk_cache_directory_
)) {
292 base::DeleteFile(disk_cache_directory_
, true);
293 if (base::DirectoryExists(disk_cache_directory_
)) {
294 database_
->Disable(); // This triggers OnFatalError handling.
299 database_
->FindLastStorageIds(
300 &last_group_id_
, &last_cache_id_
, &last_response_id_
,
301 &last_deletable_response_rowid_
);
302 database_
->GetAllOriginUsage(&usage_map_
);
305 void AppCacheStorageImpl::InitTask::RunCompleted() {
306 storage_
->last_group_id_
= last_group_id_
;
307 storage_
->last_cache_id_
= last_cache_id_
;
308 storage_
->last_response_id_
= last_response_id_
;
309 storage_
->last_deletable_response_rowid_
= last_deletable_response_rowid_
;
311 if (!storage_
->is_disabled()) {
312 storage_
->usage_map_
.swap(usage_map_
);
313 const base::TimeDelta kDelay
= base::TimeDelta::FromMinutes(5);
314 base::MessageLoop::current()->PostDelayedTask(
316 base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses
,
317 storage_
->weak_factory_
.GetWeakPtr()),
321 if (storage_
->service()->quota_client())
322 storage_
->service()->quota_client()->NotifyAppCacheReady();
325 // DisableDatabaseTask -------
327 class AppCacheStorageImpl::DisableDatabaseTask
: public DatabaseTask
{
329 explicit DisableDatabaseTask(AppCacheStorageImpl
* storage
)
330 : DatabaseTask(storage
) {}
333 void Run() override
{ database_
->Disable(); }
336 ~DisableDatabaseTask() override
{}
339 // GetAllInfoTask -------
341 class AppCacheStorageImpl::GetAllInfoTask
: public DatabaseTask
{
343 explicit GetAllInfoTask(AppCacheStorageImpl
* storage
)
344 : DatabaseTask(storage
),
345 info_collection_(new AppCacheInfoCollection()) {
350 void RunCompleted() override
;
353 ~GetAllInfoTask() override
{}
356 scoped_refptr
<AppCacheInfoCollection
> info_collection_
;
359 void AppCacheStorageImpl::GetAllInfoTask::Run() {
360 std::set
<GURL
> origins
;
361 database_
->FindOriginsWithGroups(&origins
);
362 for (std::set
<GURL
>::const_iterator origin
= origins
.begin();
363 origin
!= origins
.end(); ++origin
) {
364 AppCacheInfoVector
& infos
=
365 info_collection_
->infos_by_origin
[*origin
];
366 std::vector
<AppCacheDatabase::GroupRecord
> groups
;
367 database_
->FindGroupsForOrigin(*origin
, &groups
);
368 for (std::vector
<AppCacheDatabase::GroupRecord
>::const_iterator
369 group
= groups
.begin();
370 group
!= groups
.end(); ++group
) {
371 AppCacheDatabase::CacheRecord cache_record
;
372 database_
->FindCacheForGroup(group
->group_id
, &cache_record
);
374 info
.manifest_url
= group
->manifest_url
;
375 info
.creation_time
= group
->creation_time
;
376 info
.size
= cache_record
.cache_size
;
377 info
.last_access_time
= group
->last_access_time
;
378 info
.last_update_time
= cache_record
.update_time
;
379 info
.cache_id
= cache_record
.cache_id
;
380 info
.group_id
= group
->group_id
;
381 info
.is_complete
= true;
382 infos
.push_back(info
);
387 void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() {
388 DCHECK_EQ(1U, delegates_
.size());
389 FOR_EACH_DELEGATE(delegates_
, OnAllInfo(info_collection_
.get()));
392 // StoreOrLoadTask -------
394 class AppCacheStorageImpl::StoreOrLoadTask
: public DatabaseTask
{
396 explicit StoreOrLoadTask(AppCacheStorageImpl
* storage
)
397 : DatabaseTask(storage
) {}
398 ~StoreOrLoadTask() override
{}
400 bool FindRelatedCacheRecords(int64 cache_id
);
401 void CreateCacheAndGroupFromRecords(
402 scoped_refptr
<AppCache
>* cache
, scoped_refptr
<AppCacheGroup
>* group
);
404 AppCacheDatabase::GroupRecord group_record_
;
405 AppCacheDatabase::CacheRecord cache_record_
;
406 std::vector
<AppCacheDatabase::EntryRecord
> entry_records_
;
407 std::vector
<AppCacheDatabase::NamespaceRecord
>
408 intercept_namespace_records_
;
409 std::vector
<AppCacheDatabase::NamespaceRecord
>
410 fallback_namespace_records_
;
411 std::vector
<AppCacheDatabase::OnlineWhiteListRecord
>
412 online_whitelist_records_
;
415 bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
417 return database_
->FindEntriesForCache(cache_id
, &entry_records_
) &&
418 database_
->FindNamespacesForCache(
419 cache_id
, &intercept_namespace_records_
,
420 &fallback_namespace_records_
) &&
421 database_
->FindOnlineWhiteListForCache(
422 cache_id
, &online_whitelist_records_
);
425 void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
426 scoped_refptr
<AppCache
>* cache
, scoped_refptr
<AppCacheGroup
>* group
) {
427 DCHECK(storage_
&& cache
&& group
);
429 (*cache
) = storage_
->working_set_
.GetCache(cache_record_
.cache_id
);
431 (*group
) = cache
->get()->owning_group();
432 DCHECK(group
->get());
433 DCHECK_EQ(group_record_
.group_id
, group
->get()->group_id());
435 // TODO(michaeln): histogram is fishing for clues to crbug/95101
436 if (!cache
->get()->GetEntry(group_record_
.manifest_url
)) {
437 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
438 AppCacheHistograms::CALLSITE_0
);
441 storage_
->NotifyStorageAccessed(group_record_
.origin
);
445 (*cache
) = new AppCache(storage_
, cache_record_
.cache_id
);
446 cache
->get()->InitializeWithDatabaseRecords(
447 cache_record_
, entry_records_
,
448 intercept_namespace_records_
,
449 fallback_namespace_records_
,
450 online_whitelist_records_
);
451 cache
->get()->set_complete(true);
453 (*group
) = storage_
->working_set_
.GetGroup(group_record_
.manifest_url
);
455 DCHECK(group_record_
.group_id
== group
->get()->group_id());
456 group
->get()->AddCache(cache
->get());
458 // TODO(michaeln): histogram is fishing for clues to crbug/95101
459 if (!cache
->get()->GetEntry(group_record_
.manifest_url
)) {
460 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
461 AppCacheHistograms::CALLSITE_1
);
464 (*group
) = new AppCacheGroup(
465 storage_
, group_record_
.manifest_url
,
466 group_record_
.group_id
);
467 group
->get()->set_creation_time(group_record_
.creation_time
);
468 group
->get()->AddCache(cache
->get());
470 // TODO(michaeln): histogram is fishing for clues to crbug/95101
471 if (!cache
->get()->GetEntry(group_record_
.manifest_url
)) {
472 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
473 AppCacheHistograms::CALLSITE_2
);
476 DCHECK(group
->get()->newest_complete_cache() == cache
->get());
478 // We have to update foriegn entries if MarkEntryAsForeignTasks
480 std::vector
<GURL
> urls
;
481 storage_
->GetPendingForeignMarkingsForCache(cache
->get()->cache_id(), &urls
);
482 for (std::vector
<GURL
>::iterator iter
= urls
.begin();
483 iter
!= urls
.end(); ++iter
) {
484 DCHECK(cache
->get()->GetEntry(*iter
));
485 cache
->get()->GetEntry(*iter
)->add_types(AppCacheEntry::FOREIGN
);
488 storage_
->NotifyStorageAccessed(group_record_
.origin
);
490 // TODO(michaeln): Maybe verify that the responses we expect to exist
491 // do actually exist in the disk_cache (and if not then what?)
494 // CacheLoadTask -------
496 class AppCacheStorageImpl::CacheLoadTask
: public StoreOrLoadTask
{
498 CacheLoadTask(int64 cache_id
, AppCacheStorageImpl
* storage
)
499 : StoreOrLoadTask(storage
), cache_id_(cache_id
),
504 void RunCompleted() override
;
507 ~CacheLoadTask() override
{}
514 void AppCacheStorageImpl::CacheLoadTask::Run() {
516 database_
->FindCache(cache_id_
, &cache_record_
) &&
517 database_
->FindGroup(cache_record_
.group_id
, &group_record_
) &&
518 FindRelatedCacheRecords(cache_id_
);
521 database_
->UpdateGroupLastAccessTime(group_record_
.group_id
,
525 void AppCacheStorageImpl::CacheLoadTask::RunCompleted() {
526 storage_
->pending_cache_loads_
.erase(cache_id_
);
527 scoped_refptr
<AppCache
> cache
;
528 scoped_refptr
<AppCacheGroup
> group
;
529 if (success_
&& !storage_
->is_disabled()) {
530 DCHECK(cache_record_
.cache_id
== cache_id_
);
531 CreateCacheAndGroupFromRecords(&cache
, &group
);
533 FOR_EACH_DELEGATE(delegates_
, OnCacheLoaded(cache
.get(), cache_id_
));
536 // GroupLoadTask -------
538 class AppCacheStorageImpl::GroupLoadTask
: public StoreOrLoadTask
{
540 GroupLoadTask(GURL manifest_url
, AppCacheStorageImpl
* storage
)
541 : StoreOrLoadTask(storage
), manifest_url_(manifest_url
),
546 void RunCompleted() override
;
549 ~GroupLoadTask() override
{}
556 void AppCacheStorageImpl::GroupLoadTask::Run() {
558 database_
->FindGroupForManifestUrl(manifest_url_
, &group_record_
) &&
559 database_
->FindCacheForGroup(group_record_
.group_id
, &cache_record_
) &&
560 FindRelatedCacheRecords(cache_record_
.cache_id
);
563 database_
->UpdateGroupLastAccessTime(group_record_
.group_id
,
567 void AppCacheStorageImpl::GroupLoadTask::RunCompleted() {
568 storage_
->pending_group_loads_
.erase(manifest_url_
);
569 scoped_refptr
<AppCacheGroup
> group
;
570 scoped_refptr
<AppCache
> cache
;
571 if (!storage_
->is_disabled()) {
573 DCHECK(group_record_
.manifest_url
== manifest_url_
);
574 CreateCacheAndGroupFromRecords(&cache
, &group
);
576 group
= storage_
->working_set_
.GetGroup(manifest_url_
);
579 new AppCacheGroup(storage_
, manifest_url_
, storage_
->NewGroupId());
583 FOR_EACH_DELEGATE(delegates_
, OnGroupLoaded(group
.get(), manifest_url_
));
586 // StoreGroupAndCacheTask -------
588 class AppCacheStorageImpl::StoreGroupAndCacheTask
: public StoreOrLoadTask
{
590 StoreGroupAndCacheTask(AppCacheStorageImpl
* storage
, AppCacheGroup
* group
,
591 AppCache
* newest_cache
);
593 void GetQuotaThenSchedule();
594 void OnQuotaCallback(storage::QuotaStatusCode status
,
600 void RunCompleted() override
;
601 void CancelCompletion() override
;
604 ~StoreGroupAndCacheTask() override
{}
607 scoped_refptr
<AppCacheGroup
> group_
;
608 scoped_refptr
<AppCache
> cache_
;
610 bool would_exceed_quota_
;
611 int64 space_available_
;
612 int64 new_origin_usage_
;
613 std::vector
<int64
> newly_deletable_response_ids_
;
616 AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
617 AppCacheStorageImpl
* storage
, AppCacheGroup
* group
, AppCache
* newest_cache
)
618 : StoreOrLoadTask(storage
), group_(group
), cache_(newest_cache
),
619 success_(false), would_exceed_quota_(false),
620 space_available_(-1), new_origin_usage_(-1) {
621 group_record_
.group_id
= group
->group_id();
622 group_record_
.manifest_url
= group
->manifest_url();
623 group_record_
.origin
= group_record_
.manifest_url
.GetOrigin();
624 newest_cache
->ToDatabaseRecords(
626 &cache_record_
, &entry_records_
,
627 &intercept_namespace_records_
,
628 &fallback_namespace_records_
,
629 &online_whitelist_records_
);
632 void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
633 storage::QuotaManager
* quota_manager
= NULL
;
634 if (storage_
->service()->quota_manager_proxy()) {
636 storage_
->service()->quota_manager_proxy()->quota_manager();
639 if (!quota_manager
) {
640 if (storage_
->service()->special_storage_policy() &&
641 storage_
->service()->special_storage_policy()->IsStorageUnlimited(
642 group_record_
.origin
))
643 space_available_
= kint64max
;
648 // We have to ask the quota manager for the value.
649 storage_
->pending_quota_queries_
.insert(this);
650 quota_manager
->GetUsageAndQuota(
651 group_record_
.origin
,
652 storage::kStorageTypeTemporary
,
653 base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback
, this));
656 void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
657 storage::QuotaStatusCode status
,
661 if (status
== storage::kQuotaStatusOk
)
662 space_available_
= std::max(static_cast<int64
>(0), quota
- usage
);
664 space_available_
= 0;
665 storage_
->pending_quota_queries_
.erase(this);
670 void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
672 sql::Connection
* connection
= database_
->db_connection();
676 sql::Transaction
transaction(connection
);
677 if (!transaction
.Begin())
680 int64 old_origin_usage
= database_
->GetOriginUsage(group_record_
.origin
);
682 AppCacheDatabase::GroupRecord existing_group
;
683 success_
= database_
->FindGroup(group_record_
.group_id
, &existing_group
);
685 group_record_
.creation_time
= base::Time::Now();
686 group_record_
.last_access_time
= base::Time::Now();
687 success_
= database_
->InsertGroup(&group_record_
);
689 DCHECK(group_record_
.group_id
== existing_group
.group_id
);
690 DCHECK(group_record_
.manifest_url
== existing_group
.manifest_url
);
691 DCHECK(group_record_
.origin
== existing_group
.origin
);
693 database_
->UpdateGroupLastAccessTime(group_record_
.group_id
,
696 AppCacheDatabase::CacheRecord cache
;
697 if (database_
->FindCacheForGroup(group_record_
.group_id
, &cache
)) {
698 // Get the set of response ids in the old cache.
699 std::set
<int64
> existing_response_ids
;
700 database_
->FindResponseIdsForCacheAsSet(cache
.cache_id
,
701 &existing_response_ids
);
703 // Remove those that remain in the new cache.
704 std::vector
<AppCacheDatabase::EntryRecord
>::const_iterator entry_iter
=
705 entry_records_
.begin();
706 while (entry_iter
!= entry_records_
.end()) {
707 existing_response_ids
.erase(entry_iter
->response_id
);
711 // The rest are deletable.
712 std::set
<int64
>::const_iterator id_iter
= existing_response_ids
.begin();
713 while (id_iter
!= existing_response_ids
.end()) {
714 newly_deletable_response_ids_
.push_back(*id_iter
);
719 database_
->DeleteCache(cache
.cache_id
) &&
720 database_
->DeleteEntriesForCache(cache
.cache_id
) &&
721 database_
->DeleteNamespacesForCache(cache
.cache_id
) &&
722 database_
->DeleteOnlineWhiteListForCache(cache
.cache_id
) &&
723 database_
->InsertDeletableResponseIds(newly_deletable_response_ids_
);
724 // TODO(michaeln): store group_id too with deletable ids
726 NOTREACHED() << "A existing group without a cache is unexpected";
732 database_
->InsertCache(&cache_record_
) &&
733 database_
->InsertEntryRecords(entry_records_
) &&
734 database_
->InsertNamespaceRecords(intercept_namespace_records_
) &&
735 database_
->InsertNamespaceRecords(fallback_namespace_records_
) &&
736 database_
->InsertOnlineWhiteListRecords(online_whitelist_records_
);
741 new_origin_usage_
= database_
->GetOriginUsage(group_record_
.origin
);
743 // Only check quota when the new usage exceeds the old usage.
744 if (new_origin_usage_
<= old_origin_usage
) {
745 success_
= transaction
.Commit();
749 // Use a simple hard-coded value when not using quota management.
750 if (space_available_
== -1) {
751 if (new_origin_usage_
> kDefaultQuota
) {
752 would_exceed_quota_
= true;
756 success_
= transaction
.Commit();
760 // Check limits based on the space availbable given to us via the
762 int64 delta
= new_origin_usage_
- old_origin_usage
;
763 if (delta
> space_available_
) {
764 would_exceed_quota_
= true;
769 success_
= transaction
.Commit();
772 void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
774 storage_
->UpdateUsageMapAndNotify(
775 group_
->manifest_url().GetOrigin(), new_origin_usage_
);
776 if (cache_
.get() != group_
->newest_complete_cache()) {
777 cache_
->set_complete(true);
778 group_
->AddCache(cache_
.get());
780 if (group_
->creation_time().is_null())
781 group_
->set_creation_time(group_record_
.creation_time
);
782 group_
->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_
);
786 OnGroupAndNewestCacheStored(
787 group_
.get(), cache_
.get(), success_
, would_exceed_quota_
));
791 // TODO(michaeln): if (would_exceed_quota_) what if the current usage
792 // also exceeds the quota? http://crbug.com/83968
795 void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
796 // Overriden to safely drop our reference to the group and cache
797 // which are not thread safe refcounted.
798 DatabaseTask::CancelCompletion();
803 // FindMainResponseTask -------
805 // Helpers for FindMainResponseTask::Run()
807 class SortByCachePreference
808 : public std::binary_function
<
809 AppCacheDatabase::EntryRecord
,
810 AppCacheDatabase::EntryRecord
,
813 SortByCachePreference(int64 preferred_id
, const std::set
<int64
>& in_use_ids
)
814 : preferred_id_(preferred_id
), in_use_ids_(in_use_ids
) {
817 const AppCacheDatabase::EntryRecord
& lhs
,
818 const AppCacheDatabase::EntryRecord
& rhs
) {
819 return compute_value(lhs
) > compute_value(rhs
);
822 int compute_value(const AppCacheDatabase::EntryRecord
& entry
) {
823 if (entry
.cache_id
== preferred_id_
)
825 else if (in_use_ids_
.find(entry
.cache_id
) != in_use_ids_
.end())
830 const std::set
<int64
>& in_use_ids_
;
834 const AppCacheDatabase::NamespaceRecord
& lhs
,
835 const AppCacheDatabase::NamespaceRecord
& rhs
) {
836 return lhs
.namespace_
.namespace_url
.spec().length() >
837 rhs
.namespace_
.namespace_url
.spec().length();
840 class NetworkNamespaceHelper
{
842 explicit NetworkNamespaceHelper(AppCacheDatabase
* database
)
843 : database_(database
) {
846 bool IsInNetworkNamespace(const GURL
& url
, int64 cache_id
) {
847 typedef std::pair
<WhiteListMap::iterator
, bool> InsertResult
;
848 InsertResult result
= namespaces_map_
.insert(
849 WhiteListMap::value_type(cache_id
, AppCacheNamespaceVector()));
851 GetOnlineWhiteListForCache(cache_id
, &result
.first
->second
);
852 return AppCache::FindNamespace(result
.first
->second
, url
) != NULL
;
856 void GetOnlineWhiteListForCache(
857 int64 cache_id
, AppCacheNamespaceVector
* namespaces
) {
858 DCHECK(namespaces
&& namespaces
->empty());
859 typedef std::vector
<AppCacheDatabase::OnlineWhiteListRecord
>
861 WhiteListVector records
;
862 if (!database_
->FindOnlineWhiteListForCache(cache_id
, &records
))
864 WhiteListVector::const_iterator iter
= records
.begin();
865 while (iter
!= records
.end()) {
866 namespaces
->push_back(
867 AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE
, iter
->namespace_url
,
868 GURL(), iter
->is_pattern
));
874 typedef std::map
<int64
, AppCacheNamespaceVector
> WhiteListMap
;
875 WhiteListMap namespaces_map_
;
876 AppCacheDatabase
* database_
;
881 class AppCacheStorageImpl::FindMainResponseTask
: public DatabaseTask
{
883 FindMainResponseTask(AppCacheStorageImpl
* storage
,
885 const GURL
& preferred_manifest_url
,
886 const AppCacheWorkingSet::GroupMap
* groups_in_use
)
887 : DatabaseTask(storage
), url_(url
),
888 preferred_manifest_url_(preferred_manifest_url
),
889 cache_id_(kAppCacheNoCacheId
), group_id_(0) {
891 for (AppCacheWorkingSet::GroupMap::const_iterator it
=
892 groups_in_use
->begin();
893 it
!= groups_in_use
->end(); ++it
) {
894 AppCacheGroup
* group
= it
->second
;
895 AppCache
* cache
= group
->newest_complete_cache();
896 if (group
->is_obsolete() || !cache
)
898 cache_ids_in_use_
.insert(cache
->cache_id());
905 void RunCompleted() override
;
908 ~FindMainResponseTask() override
{}
911 typedef std::vector
<AppCacheDatabase::NamespaceRecord
*>
912 NamespaceRecordPtrVector
;
914 bool FindExactMatch(int64 preferred_id
);
915 bool FindNamespaceMatch(int64 preferred_id
);
916 bool FindNamespaceHelper(
917 int64 preferred_cache_id
,
918 AppCacheDatabase::NamespaceRecordVector
* namespaces
,
919 NetworkNamespaceHelper
* network_namespace_helper
);
920 bool FindFirstValidNamespace(const NamespaceRecordPtrVector
& namespaces
);
923 GURL preferred_manifest_url_
;
924 std::set
<int64
> cache_ids_in_use_
;
925 AppCacheEntry entry_
;
926 AppCacheEntry fallback_entry_
;
927 GURL namespace_entry_url_
;
933 void AppCacheStorageImpl::FindMainResponseTask::Run() {
934 // NOTE: The heuristics around choosing amoungst multiple candidates
935 // is underspecified, and just plain not fully understood. This needs
938 // The 'preferred_manifest_url' is the url of the manifest associated
939 // with the page that opened or embedded the page being loaded now.
940 // We have a strong preference to use resources from that cache.
941 // We also have a lesser bias to use resources from caches that are currently
942 // being used by other unrelated pages.
943 // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases
944 // - when navigating a frame whose current contents are from an appcache
945 // - when clicking an href in a frame that is appcached
946 int64 preferred_cache_id
= kAppCacheNoCacheId
;
947 if (!preferred_manifest_url_
.is_empty()) {
948 AppCacheDatabase::GroupRecord preferred_group
;
949 AppCacheDatabase::CacheRecord preferred_cache
;
950 if (database_
->FindGroupForManifestUrl(
951 preferred_manifest_url_
, &preferred_group
) &&
952 database_
->FindCacheForGroup(
953 preferred_group
.group_id
, &preferred_cache
)) {
954 preferred_cache_id
= preferred_cache
.cache_id
;
958 if (FindExactMatch(preferred_cache_id
) ||
959 FindNamespaceMatch(preferred_cache_id
)) {
960 // We found something.
961 DCHECK(cache_id_
!= kAppCacheNoCacheId
&& !manifest_url_
.is_empty() &&
966 // We didn't find anything.
967 DCHECK(cache_id_
== kAppCacheNoCacheId
&& manifest_url_
.is_empty() &&
971 bool AppCacheStorageImpl::
972 FindMainResponseTask::FindExactMatch(int64 preferred_cache_id
) {
973 std::vector
<AppCacheDatabase::EntryRecord
> entries
;
974 if (database_
->FindEntriesForUrl(url_
, &entries
) && !entries
.empty()) {
975 // Sort them in order of preference, from the preferred_cache first,
976 // followed by hits from caches that are 'in use', then the rest.
977 std::sort(entries
.begin(), entries
.end(),
978 SortByCachePreference(preferred_cache_id
, cache_ids_in_use_
));
980 // Take the first with a valid, non-foreign entry.
981 std::vector
<AppCacheDatabase::EntryRecord
>::iterator iter
;
982 for (iter
= entries
.begin(); iter
< entries
.end(); ++iter
) {
983 AppCacheDatabase::GroupRecord group_record
;
984 if ((iter
->flags
& AppCacheEntry::FOREIGN
) ||
985 !database_
->FindGroupForCache(iter
->cache_id
, &group_record
)) {
988 manifest_url_
= group_record
.manifest_url
;
989 group_id_
= group_record
.group_id
;
990 entry_
= AppCacheEntry(iter
->flags
, iter
->response_id
);
991 cache_id_
= iter
->cache_id
;
992 return true; // We found an exact match.
998 bool AppCacheStorageImpl::
999 FindMainResponseTask::FindNamespaceMatch(int64 preferred_cache_id
) {
1000 AppCacheDatabase::NamespaceRecordVector all_intercepts
;
1001 AppCacheDatabase::NamespaceRecordVector all_fallbacks
;
1002 if (!database_
->FindNamespacesForOrigin(
1003 url_
.GetOrigin(), &all_intercepts
, &all_fallbacks
)
1004 || (all_intercepts
.empty() && all_fallbacks
.empty())) {
1008 NetworkNamespaceHelper
network_namespace_helper(database_
);
1009 if (FindNamespaceHelper(preferred_cache_id
,
1011 &network_namespace_helper
) ||
1012 FindNamespaceHelper(preferred_cache_id
,
1014 &network_namespace_helper
)) {
1020 bool AppCacheStorageImpl::
1021 FindMainResponseTask::FindNamespaceHelper(
1022 int64 preferred_cache_id
,
1023 AppCacheDatabase::NamespaceRecordVector
* namespaces
,
1024 NetworkNamespaceHelper
* network_namespace_helper
) {
1025 // Sort them by length, longer matches within the same cache/bucket take
1027 std::sort(namespaces
->begin(), namespaces
->end(), SortByLength
);
1029 NamespaceRecordPtrVector preferred_namespaces
;
1030 NamespaceRecordPtrVector inuse_namespaces
;
1031 NamespaceRecordPtrVector other_namespaces
;
1032 std::vector
<AppCacheDatabase::NamespaceRecord
>::iterator iter
;
1033 for (iter
= namespaces
->begin(); iter
< namespaces
->end(); ++iter
) {
1034 // Skip those that aren't a match.
1035 if (!iter
->namespace_
.IsMatch(url_
))
1038 // Skip namespaces where the requested url falls into a network
1039 // namespace of its containing appcache.
1040 if (network_namespace_helper
->IsInNetworkNamespace(url_
, iter
->cache_id
))
1043 // Bin them into one of our three buckets.
1044 if (iter
->cache_id
== preferred_cache_id
)
1045 preferred_namespaces
.push_back(&(*iter
));
1046 else if (cache_ids_in_use_
.find(iter
->cache_id
) != cache_ids_in_use_
.end())
1047 inuse_namespaces
.push_back(&(*iter
));
1049 other_namespaces
.push_back(&(*iter
));
1052 if (FindFirstValidNamespace(preferred_namespaces
) ||
1053 FindFirstValidNamespace(inuse_namespaces
) ||
1054 FindFirstValidNamespace(other_namespaces
))
1055 return true; // We found one.
1057 // We didn't find anything.
1061 bool AppCacheStorageImpl::
1062 FindMainResponseTask::FindFirstValidNamespace(
1063 const NamespaceRecordPtrVector
& namespaces
) {
1064 // Take the first with a valid, non-foreign entry.
1065 NamespaceRecordPtrVector::const_iterator iter
;
1066 for (iter
= namespaces
.begin(); iter
< namespaces
.end(); ++iter
) {
1067 AppCacheDatabase::EntryRecord entry_record
;
1068 if (database_
->FindEntry((*iter
)->cache_id
, (*iter
)->namespace_
.target_url
,
1070 AppCacheDatabase::GroupRecord group_record
;
1071 if ((entry_record
.flags
& AppCacheEntry::FOREIGN
) ||
1072 !database_
->FindGroupForCache(entry_record
.cache_id
, &group_record
)) {
1075 manifest_url_
= group_record
.manifest_url
;
1076 group_id_
= group_record
.group_id
;
1077 cache_id_
= (*iter
)->cache_id
;
1078 namespace_entry_url_
= (*iter
)->namespace_
.target_url
;
1079 if ((*iter
)->namespace_
.type
== APPCACHE_FALLBACK_NAMESPACE
)
1080 fallback_entry_
= AppCacheEntry(entry_record
.flags
,
1081 entry_record
.response_id
);
1083 entry_
= AppCacheEntry(entry_record
.flags
, entry_record
.response_id
);
1084 return true; // We found one.
1087 return false; // We didn't find a match.
1090 void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
1091 storage_
->CallOnMainResponseFound(
1092 &delegates_
, url_
, entry_
, namespace_entry_url_
, fallback_entry_
,
1093 cache_id_
, group_id_
, manifest_url_
);
1096 // MarkEntryAsForeignTask -------
1098 class AppCacheStorageImpl::MarkEntryAsForeignTask
: public DatabaseTask
{
1100 MarkEntryAsForeignTask(
1101 AppCacheStorageImpl
* storage
, const GURL
& url
, int64 cache_id
)
1102 : DatabaseTask(storage
), cache_id_(cache_id
), entry_url_(url
) {}
1105 void Run() override
;
1106 void RunCompleted() override
;
1109 ~MarkEntryAsForeignTask() override
{}
1116 void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() {
1117 database_
->AddEntryFlags(entry_url_
, cache_id_
, AppCacheEntry::FOREIGN
);
1120 void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() {
1121 DCHECK(storage_
->pending_foreign_markings_
.front().first
== entry_url_
&&
1122 storage_
->pending_foreign_markings_
.front().second
== cache_id_
);
1123 storage_
->pending_foreign_markings_
.pop_front();
1126 // MakeGroupObsoleteTask -------
1128 class AppCacheStorageImpl::MakeGroupObsoleteTask
: public DatabaseTask
{
1130 MakeGroupObsoleteTask(AppCacheStorageImpl
* storage
,
1131 AppCacheGroup
* group
,
1135 void Run() override
;
1136 void RunCompleted() override
;
1137 void CancelCompletion() override
;
1140 ~MakeGroupObsoleteTask() override
{}
1143 scoped_refptr
<AppCacheGroup
> group_
;
1148 int64 new_origin_usage_
;
1149 std::vector
<int64
> newly_deletable_response_ids_
;
1152 AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
1153 AppCacheStorageImpl
* storage
,
1154 AppCacheGroup
* group
,
1156 : DatabaseTask(storage
),
1158 group_id_(group
->group_id()),
1159 origin_(group
->manifest_url().GetOrigin()),
1161 response_code_(response_code
),
1162 new_origin_usage_(-1) {}
1164 void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
1166 sql::Connection
* connection
= database_
->db_connection();
1170 sql::Transaction
transaction(connection
);
1171 if (!transaction
.Begin())
1174 AppCacheDatabase::GroupRecord group_record
;
1175 if (!database_
->FindGroup(group_id_
, &group_record
)) {
1176 // This group doesn't exists in the database, nothing todo here.
1177 new_origin_usage_
= database_
->GetOriginUsage(origin_
);
1182 DCHECK_EQ(group_record
.origin
, origin_
);
1183 success_
= DeleteGroupAndRelatedRecords(database_
,
1185 &newly_deletable_response_ids_
);
1187 new_origin_usage_
= database_
->GetOriginUsage(origin_
);
1188 success_
= success_
&& transaction
.Commit();
1191 void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
1193 group_
->set_obsolete(true);
1194 if (!storage_
->is_disabled()) {
1195 storage_
->UpdateUsageMapAndNotify(origin_
, new_origin_usage_
);
1196 group_
->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_
);
1198 // Also remove from the working set, caches for an 'obsolete' group
1199 // may linger in use, but the group itself cannot be looked up by
1200 // 'manifest_url' in the working set any longer.
1201 storage_
->working_set()->RemoveGroup(group_
.get());
1205 delegates_
, OnGroupMadeObsolete(group_
.get(), success_
, response_code_
));
1209 void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() {
1210 // Overriden to safely drop our reference to the group
1211 // which is not thread safe refcounted.
1212 DatabaseTask::CancelCompletion();
1216 // GetDeletableResponseIdsTask -------
1218 class AppCacheStorageImpl::GetDeletableResponseIdsTask
: public DatabaseTask
{
1220 GetDeletableResponseIdsTask(AppCacheStorageImpl
* storage
, int64 max_rowid
)
1221 : DatabaseTask(storage
), max_rowid_(max_rowid
) {}
1224 void Run() override
;
1225 void RunCompleted() override
;
1228 ~GetDeletableResponseIdsTask() override
{}
1232 std::vector
<int64
> response_ids_
;
1235 void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() {
1236 const int kSqlLimit
= 1000;
1237 database_
->GetDeletableResponseIds(&response_ids_
, max_rowid_
, kSqlLimit
);
1238 // TODO(michaeln): retrieve group_ids too
1241 void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() {
1242 if (!response_ids_
.empty())
1243 storage_
->StartDeletingResponses(response_ids_
);
1246 // InsertDeletableResponseIdsTask -------
1248 class AppCacheStorageImpl::InsertDeletableResponseIdsTask
1249 : public DatabaseTask
{
1251 explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl
* storage
)
1252 : DatabaseTask(storage
) {}
1255 void Run() override
;
1257 std::vector
<int64
> response_ids_
;
1260 ~InsertDeletableResponseIdsTask() override
{}
1263 void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() {
1264 database_
->InsertDeletableResponseIds(response_ids_
);
1265 // TODO(michaeln): store group_ids too
1268 // DeleteDeletableResponseIdsTask -------
1270 class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
1271 : public DatabaseTask
{
1273 explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl
* storage
)
1274 : DatabaseTask(storage
) {}
1277 void Run() override
;
1279 std::vector
<int64
> response_ids_
;
1282 ~DeleteDeletableResponseIdsTask() override
{}
1285 void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
1286 database_
->DeleteDeletableResponseIds(response_ids_
);
1289 // UpdateGroupLastAccessTimeTask -------
1291 class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask
1292 : public DatabaseTask
{
1294 UpdateGroupLastAccessTimeTask(
1295 AppCacheStorageImpl
* storage
, AppCacheGroup
* group
, base::Time time
)
1296 : DatabaseTask(storage
), group_id_(group
->group_id()),
1297 last_access_time_(time
) {
1298 storage
->NotifyStorageAccessed(group
->manifest_url().GetOrigin());
1302 void Run() override
;
1305 ~UpdateGroupLastAccessTimeTask() override
{}
1309 base::Time last_access_time_
;
1312 void AppCacheStorageImpl::UpdateGroupLastAccessTimeTask::Run() {
1313 database_
->UpdateGroupLastAccessTime(group_id_
, last_access_time_
);
1317 // AppCacheStorageImpl ---------------------------------------------------
1319 AppCacheStorageImpl::AppCacheStorageImpl(AppCacheServiceImpl
* service
)
1320 : AppCacheStorage(service
),
1321 is_incognito_(false),
1322 is_response_deletion_scheduled_(false),
1323 did_start_deleting_responses_(false),
1324 last_deletable_response_rowid_(0),
1326 is_disabled_(false),
1327 weak_factory_(this) {
1330 AppCacheStorageImpl::~AppCacheStorageImpl() {
1331 std::for_each(pending_quota_queries_
.begin(),
1332 pending_quota_queries_
.end(),
1333 std::mem_fun(&DatabaseTask::CancelCompletion
));
1334 std::for_each(scheduled_database_tasks_
.begin(),
1335 scheduled_database_tasks_
.end(),
1336 std::mem_fun(&DatabaseTask::CancelCompletion
));
1339 !db_thread_
->PostTask(
1341 base::Bind(&ClearSessionOnlyOrigins
,
1343 make_scoped_refptr(service_
->special_storage_policy()),
1344 service()->force_keep_session_state()))) {
1347 database_
= NULL
; // So no further database tasks can be scheduled.
1350 void AppCacheStorageImpl::Initialize(
1351 const base::FilePath
& cache_directory
,
1352 const scoped_refptr
<base::SingleThreadTaskRunner
>& db_thread
,
1353 const scoped_refptr
<base::SingleThreadTaskRunner
>& cache_thread
) {
1354 DCHECK(db_thread
.get());
1356 cache_directory_
= cache_directory
;
1357 is_incognito_
= cache_directory_
.empty();
1359 base::FilePath db_file_path
;
1361 db_file_path
= cache_directory_
.Append(kAppCacheDatabaseName
);
1362 database_
= new AppCacheDatabase(db_file_path
);
1364 db_thread_
= db_thread
;
1365 cache_thread_
= cache_thread
;
1367 scoped_refptr
<InitTask
> task(new InitTask(this));
1371 void AppCacheStorageImpl::Disable() {
1374 VLOG(1) << "Disabling appcache storage.";
1375 is_disabled_
= true;
1376 ClearUsageMapAndNotify();
1377 working_set()->Disable();
1379 disk_cache_
->Disable();
1380 scoped_refptr
<DisableDatabaseTask
> task(new DisableDatabaseTask(this));
1384 void AppCacheStorageImpl::GetAllInfo(Delegate
* delegate
) {
1386 scoped_refptr
<GetAllInfoTask
> task(new GetAllInfoTask(this));
1387 task
->AddDelegate(GetOrCreateDelegateReference(delegate
));
1391 void AppCacheStorageImpl::LoadCache(int64 id
, Delegate
* delegate
) {
1394 delegate
->OnCacheLoaded(NULL
, id
);
1398 AppCache
* cache
= working_set_
.GetCache(id
);
1400 delegate
->OnCacheLoaded(cache
, id
);
1401 if (cache
->owning_group()) {
1402 scoped_refptr
<DatabaseTask
> update_task(
1403 new UpdateGroupLastAccessTimeTask(
1404 this, cache
->owning_group(), base::Time::Now()));
1405 update_task
->Schedule();
1409 scoped_refptr
<CacheLoadTask
> task(GetPendingCacheLoadTask(id
));
1411 task
->AddDelegate(GetOrCreateDelegateReference(delegate
));
1414 task
= new CacheLoadTask(id
, this);
1415 task
->AddDelegate(GetOrCreateDelegateReference(delegate
));
1417 pending_cache_loads_
[id
] = task
.get();
1420 void AppCacheStorageImpl::LoadOrCreateGroup(
1421 const GURL
& manifest_url
, Delegate
* delegate
) {
1424 delegate
->OnGroupLoaded(NULL
, manifest_url
);
1428 AppCacheGroup
* group
= working_set_
.GetGroup(manifest_url
);
1430 delegate
->OnGroupLoaded(group
, manifest_url
);
1431 scoped_refptr
<DatabaseTask
> update_task(
1432 new UpdateGroupLastAccessTimeTask(
1433 this, group
, base::Time::Now()));
1434 update_task
->Schedule();
1438 scoped_refptr
<GroupLoadTask
> task(GetPendingGroupLoadTask(manifest_url
));
1440 task
->AddDelegate(GetOrCreateDelegateReference(delegate
));
1444 if (usage_map_
.find(manifest_url
.GetOrigin()) == usage_map_
.end()) {
1445 // No need to query the database, return a new group immediately.
1446 scoped_refptr
<AppCacheGroup
> group(new AppCacheGroup(
1447 this, manifest_url
, NewGroupId()));
1448 delegate
->OnGroupLoaded(group
.get(), manifest_url
);
1452 task
= new GroupLoadTask(manifest_url
, this);
1453 task
->AddDelegate(GetOrCreateDelegateReference(delegate
));
1455 pending_group_loads_
[manifest_url
] = task
.get();
1458 void AppCacheStorageImpl::StoreGroupAndNewestCache(
1459 AppCacheGroup
* group
, AppCache
* newest_cache
, Delegate
* delegate
) {
1460 // TODO(michaeln): distinguish between a simple update of an existing
1461 // cache that just adds new master entry(s), and the insertion of a
1462 // whole new cache. The StoreGroupAndCacheTask as written will handle
1463 // the simple update case in a very heavy weight way (delete all and
1464 // the reinsert all over again).
1465 DCHECK(group
&& delegate
&& newest_cache
);
1466 scoped_refptr
<StoreGroupAndCacheTask
> task(
1467 new StoreGroupAndCacheTask(this, group
, newest_cache
));
1468 task
->AddDelegate(GetOrCreateDelegateReference(delegate
));
1469 task
->GetQuotaThenSchedule();
1471 // TODO(michaeln): histogram is fishing for clues to crbug/95101
1472 if (!newest_cache
->GetEntry(group
->manifest_url())) {
1473 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
1474 AppCacheHistograms::CALLSITE_3
);
1478 void AppCacheStorageImpl::FindResponseForMainRequest(
1479 const GURL
& url
, const GURL
& preferred_manifest_url
,
1480 Delegate
* delegate
) {
1483 const GURL
* url_ptr
= &url
;
1485 if (url
.has_ref()) {
1486 GURL::Replacements replacements
;
1487 replacements
.ClearRef();
1488 url_no_ref
= url
.ReplaceComponents(replacements
);
1489 url_ptr
= &url_no_ref
;
1492 const GURL origin
= url
.GetOrigin();
1494 // First look in our working set for a direct hit without having to query
1496 const AppCacheWorkingSet::GroupMap
* groups_in_use
=
1497 working_set()->GetGroupsInOrigin(origin
);
1498 if (groups_in_use
) {
1499 if (!preferred_manifest_url
.is_empty()) {
1500 AppCacheWorkingSet::GroupMap::const_iterator found
=
1501 groups_in_use
->find(preferred_manifest_url
);
1502 if (found
!= groups_in_use
->end() &&
1503 FindResponseForMainRequestInGroup(
1504 found
->second
, *url_ptr
, delegate
)) {
1508 for (AppCacheWorkingSet::GroupMap::const_iterator it
=
1509 groups_in_use
->begin();
1510 it
!= groups_in_use
->end(); ++it
) {
1511 if (FindResponseForMainRequestInGroup(
1512 it
->second
, *url_ptr
, delegate
)) {
1519 if (IsInitTaskComplete() && usage_map_
.find(origin
) == usage_map_
.end()) {
1520 // No need to query the database, return async'ly but without going thru
1522 scoped_refptr
<AppCacheGroup
> no_group
;
1523 scoped_refptr
<AppCache
> no_cache
;
1525 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse
,
1526 weak_factory_
.GetWeakPtr(), url
, AppCacheEntry(), no_group
,
1528 make_scoped_refptr(GetOrCreateDelegateReference(delegate
))));
1532 // We have to query the database, schedule a database task to do so.
1533 scoped_refptr
<FindMainResponseTask
> task(
1534 new FindMainResponseTask(this, *url_ptr
, preferred_manifest_url
,
1536 task
->AddDelegate(GetOrCreateDelegateReference(delegate
));
1540 bool AppCacheStorageImpl::FindResponseForMainRequestInGroup(
1541 AppCacheGroup
* group
, const GURL
& url
, Delegate
* delegate
) {
1542 AppCache
* cache
= group
->newest_complete_cache();
1543 if (group
->is_obsolete() || !cache
)
1546 AppCacheEntry
* entry
= cache
->GetEntry(url
);
1547 if (!entry
|| entry
->IsForeign())
1551 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse
,
1552 weak_factory_
.GetWeakPtr(), url
, *entry
,
1553 make_scoped_refptr(group
), make_scoped_refptr(cache
),
1554 make_scoped_refptr(GetOrCreateDelegateReference(delegate
))));
1558 void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
1560 const AppCacheEntry
& found_entry
,
1561 scoped_refptr
<AppCacheGroup
> group
,
1562 scoped_refptr
<AppCache
> cache
,
1563 scoped_refptr
<DelegateReference
> delegate_ref
) {
1564 if (delegate_ref
->delegate
) {
1565 DelegateReferenceVector
delegates(1, delegate_ref
);
1566 CallOnMainResponseFound(
1567 &delegates
, url
, found_entry
,
1568 GURL(), AppCacheEntry(),
1569 cache
.get() ? cache
->cache_id() : kAppCacheNoCacheId
,
1570 group
.get() ? group
->group_id() : kAppCacheNoCacheId
,
1571 group
.get() ? group
->manifest_url() : GURL());
1575 void AppCacheStorageImpl::CallOnMainResponseFound(
1576 DelegateReferenceVector
* delegates
,
1577 const GURL
& url
, const AppCacheEntry
& entry
,
1578 const GURL
& namespace_entry_url
, const AppCacheEntry
& fallback_entry
,
1579 int64 cache_id
, int64 group_id
, const GURL
& manifest_url
) {
1582 OnMainResponseFound(url
, entry
,
1583 namespace_entry_url
, fallback_entry
,
1584 cache_id
, group_id
, manifest_url
));
1587 void AppCacheStorageImpl::FindResponseForSubRequest(
1588 AppCache
* cache
, const GURL
& url
,
1589 AppCacheEntry
* found_entry
, AppCacheEntry
* found_fallback_entry
,
1590 bool* found_network_namespace
) {
1591 DCHECK(cache
&& cache
->is_complete());
1593 // When a group is forcibly deleted, all subresource loads for pages
1594 // using caches in the group will result in a synthesized network errors.
1595 // Forcible deletion is not a function that is covered by the HTML5 spec.
1596 if (cache
->owning_group()->is_being_deleted()) {
1597 *found_entry
= AppCacheEntry();
1598 *found_fallback_entry
= AppCacheEntry();
1599 *found_network_namespace
= false;
1603 GURL fallback_namespace_not_used
;
1604 GURL intercept_namespace_not_used
;
1605 cache
->FindResponseForRequest(
1606 url
, found_entry
, &intercept_namespace_not_used
,
1607 found_fallback_entry
, &fallback_namespace_not_used
,
1608 found_network_namespace
);
1611 void AppCacheStorageImpl::MarkEntryAsForeign(
1612 const GURL
& entry_url
, int64 cache_id
) {
1613 AppCache
* cache
= working_set_
.GetCache(cache_id
);
1615 AppCacheEntry
* entry
= cache
->GetEntry(entry_url
);
1618 entry
->add_types(AppCacheEntry::FOREIGN
);
1620 scoped_refptr
<MarkEntryAsForeignTask
> task(
1621 new MarkEntryAsForeignTask(this, entry_url
, cache_id
));
1623 pending_foreign_markings_
.push_back(std::make_pair(entry_url
, cache_id
));
1626 void AppCacheStorageImpl::MakeGroupObsolete(AppCacheGroup
* group
,
1628 int response_code
) {
1629 DCHECK(group
&& delegate
);
1630 scoped_refptr
<MakeGroupObsoleteTask
> task(
1631 new MakeGroupObsoleteTask(this, group
, response_code
));
1632 task
->AddDelegate(GetOrCreateDelegateReference(delegate
));
1636 AppCacheResponseReader
* AppCacheStorageImpl::CreateResponseReader(
1637 const GURL
& manifest_url
, int64 group_id
, int64 response_id
) {
1638 return new AppCacheResponseReader(response_id
, group_id
, disk_cache());
1641 AppCacheResponseWriter
* AppCacheStorageImpl::CreateResponseWriter(
1642 const GURL
& manifest_url
, int64 group_id
) {
1643 return new AppCacheResponseWriter(NewResponseId(), group_id
, disk_cache());
1646 void AppCacheStorageImpl::DoomResponses(
1647 const GURL
& manifest_url
, const std::vector
<int64
>& response_ids
) {
1648 if (response_ids
.empty())
1651 // Start deleting them from the disk cache lazily.
1652 StartDeletingResponses(response_ids
);
1654 // Also schedule a database task to record these ids in the
1655 // deletable responses table.
1656 // TODO(michaeln): There is a race here. If the browser crashes
1657 // prior to committing these rows to the database and prior to us
1658 // having deleted them from the disk cache, we'll never delete them.
1659 scoped_refptr
<InsertDeletableResponseIdsTask
> task(
1660 new InsertDeletableResponseIdsTask(this));
1661 task
->response_ids_
= response_ids
;
1665 void AppCacheStorageImpl::DeleteResponses(
1666 const GURL
& manifest_url
, const std::vector
<int64
>& response_ids
) {
1667 if (response_ids
.empty())
1669 StartDeletingResponses(response_ids
);
1672 void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() {
1673 // Only if we haven't already begun.
1674 if (!did_start_deleting_responses_
) {
1675 scoped_refptr
<GetDeletableResponseIdsTask
> task(
1676 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_
));
1681 void AppCacheStorageImpl::StartDeletingResponses(
1682 const std::vector
<int64
>& response_ids
) {
1683 DCHECK(!response_ids
.empty());
1684 did_start_deleting_responses_
= true;
1685 deletable_response_ids_
.insert(
1686 deletable_response_ids_
.end(),
1687 response_ids
.begin(), response_ids
.end());
1688 if (!is_response_deletion_scheduled_
)
1689 ScheduleDeleteOneResponse();
1692 void AppCacheStorageImpl::ScheduleDeleteOneResponse() {
1693 DCHECK(!is_response_deletion_scheduled_
);
1694 const base::TimeDelta kDelay
= base::TimeDelta::FromMilliseconds(10);
1695 base::MessageLoop::current()->PostDelayedTask(
1697 base::Bind(&AppCacheStorageImpl::DeleteOneResponse
,
1698 weak_factory_
.GetWeakPtr()),
1700 is_response_deletion_scheduled_
= true;
1703 void AppCacheStorageImpl::DeleteOneResponse() {
1704 DCHECK(is_response_deletion_scheduled_
);
1705 DCHECK(!deletable_response_ids_
.empty());
1707 if (!disk_cache()) {
1708 DCHECK(is_disabled_
);
1709 deletable_response_ids_
.clear();
1710 deleted_response_ids_
.clear();
1711 is_response_deletion_scheduled_
= false;
1715 // TODO(michaeln): add group_id to DoomEntry args
1716 int64 id
= deletable_response_ids_
.front();
1717 int rv
= disk_cache_
->DoomEntry(
1718 id
, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse
,
1719 base::Unretained(this)));
1720 if (rv
!= net::ERR_IO_PENDING
)
1721 OnDeletedOneResponse(rv
);
1724 void AppCacheStorageImpl::OnDeletedOneResponse(int rv
) {
1725 is_response_deletion_scheduled_
= false;
1729 int64 id
= deletable_response_ids_
.front();
1730 deletable_response_ids_
.pop_front();
1731 if (rv
!= net::ERR_ABORTED
)
1732 deleted_response_ids_
.push_back(id
);
1734 const size_t kBatchSize
= 50U;
1735 if (deleted_response_ids_
.size() >= kBatchSize
||
1736 deletable_response_ids_
.empty()) {
1737 scoped_refptr
<DeleteDeletableResponseIdsTask
> task(
1738 new DeleteDeletableResponseIdsTask(this));
1739 task
->response_ids_
.swap(deleted_response_ids_
);
1743 if (deletable_response_ids_
.empty()) {
1744 scoped_refptr
<GetDeletableResponseIdsTask
> task(
1745 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_
));
1750 ScheduleDeleteOneResponse();
1753 AppCacheStorageImpl::CacheLoadTask
*
1754 AppCacheStorageImpl::GetPendingCacheLoadTask(int64 cache_id
) {
1755 PendingCacheLoads::iterator found
= pending_cache_loads_
.find(cache_id
);
1756 if (found
!= pending_cache_loads_
.end())
1757 return found
->second
;
1761 AppCacheStorageImpl::GroupLoadTask
*
1762 AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL
& manifest_url
) {
1763 PendingGroupLoads::iterator found
= pending_group_loads_
.find(manifest_url
);
1764 if (found
!= pending_group_loads_
.end())
1765 return found
->second
;
1769 void AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
1770 int64 cache_id
, std::vector
<GURL
>* urls
) {
1771 PendingForeignMarkings::iterator iter
= pending_foreign_markings_
.begin();
1772 while (iter
!= pending_foreign_markings_
.end()) {
1773 if (iter
->second
== cache_id
)
1774 urls
->push_back(iter
->first
);
1779 void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure
& task
) {
1780 pending_simple_tasks_
.push_back(task
);
1781 base::MessageLoop::current()->PostTask(
1783 base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask
,
1784 weak_factory_
.GetWeakPtr()));
1787 void AppCacheStorageImpl::RunOnePendingSimpleTask() {
1788 DCHECK(!pending_simple_tasks_
.empty());
1789 base::Closure task
= pending_simple_tasks_
.front();
1790 pending_simple_tasks_
.pop_front();
1794 AppCacheDiskCache
* AppCacheStorageImpl::disk_cache() {
1795 DCHECK(IsInitTaskComplete());
1802 disk_cache_
.reset(new AppCacheDiskCache
);
1803 if (is_incognito_
) {
1804 rv
= disk_cache_
->InitWithMemBackend(
1805 kMaxMemDiskCacheSize
,
1806 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized
,
1807 base::Unretained(this)));
1809 rv
= disk_cache_
->InitWithDiskBackend(
1810 cache_directory_
.Append(kDiskCacheDirectoryName
),
1813 cache_thread_
.get(),
1814 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized
,
1815 base::Unretained(this)));
1818 if (rv
!= net::ERR_IO_PENDING
)
1819 OnDiskCacheInitialized(rv
);
1821 return disk_cache_
.get();
1824 void AppCacheStorageImpl::OnDiskCacheInitialized(int rv
) {
1825 if (rv
!= net::OK
) {
1826 LOG(ERROR
) << "Failed to open the appcache diskcache.";
1827 AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR
);
1829 // We're unable to open the disk cache, this is a fatal error that we can't
1830 // really recover from. We handle it by temporarily disabling the appcache
1831 // deleting the directory on disk and reinitializing the appcache system.
1833 if (rv
!= net::ERR_ABORTED
)
1834 DeleteAndStartOver();
1838 void AppCacheStorageImpl::DeleteAndStartOver() {
1839 DCHECK(is_disabled_
);
1840 if (!is_incognito_
) {
1841 VLOG(1) << "Deleting existing appcache data and starting over.";
1842 // We can have tasks in flight to close file handles on both the db
1843 // and cache threads, we need to allow those tasks to cycle thru
1844 // prior to deleting the files and calling reinit.
1845 cache_thread_
->PostTaskAndReply(
1847 base::Bind(&base::DoNothing
),
1848 base::Bind(&AppCacheStorageImpl::DeleteAndStartOverPart2
,
1849 weak_factory_
.GetWeakPtr()));
1853 void AppCacheStorageImpl::DeleteAndStartOverPart2() {
1854 db_thread_
->PostTaskAndReply(
1856 base::Bind(base::IgnoreResult(&base::DeleteFile
), cache_directory_
, true),
1857 base::Bind(&AppCacheStorageImpl::CallScheduleReinitialize
,
1858 weak_factory_
.GetWeakPtr()));
1861 void AppCacheStorageImpl::CallScheduleReinitialize() {
1862 service_
->ScheduleReinitialize();
1863 // note: 'this' may be deleted at this point.
1866 } // namespace content