Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / content / browser / appcache / appcache_storage_impl.cc
blob6e1388be2f53e69cd983329be43947019d776f05
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"
7 #include <algorithm>
8 #include <functional>
9 #include <set>
10 #include <vector>
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/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 "webkit/browser/quota/quota_client.h"
33 #include "webkit/browser/quota/quota_manager.h"
34 #include "webkit/browser/quota/quota_manager_proxy.h"
35 #include "webkit/browser/quota/special_storage_policy.h"
37 namespace content {
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");
47 namespace {
49 // Helpers for clearing data from the AppCacheDatabase.
50 bool DeleteGroupAndRelatedRecords(AppCacheDatabase* database,
51 int64 group_id,
52 std::vector<int64>* deletable_response_ids) {
53 AppCacheDatabase::CacheRecord cache_record;
54 bool success = false;
55 if (database->FindCacheForGroup(group_id, &cache_record)) {
56 database->FindResponseIdsForCacheAsVector(cache_record.cache_id,
57 deletable_response_ids);
58 success =
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);
65 } else {
66 NOTREACHED() << "A existing group without a cache is unexpected";
67 success = database->DeleteGroup(group_id);
69 return success;
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)
82 return;
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)
90 return;
92 std::set<GURL> origins;
93 database->FindOriginsWithGroups(&origins);
94 if (origins.empty())
95 return; // nothing to delete
97 sql::Connection* connection = database->db_connection();
98 if (!connection) {
99 NOTREACHED() << "Missing database connection.";
100 return;
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))
107 continue;
108 if (special_storage_policy->IsStorageProtected(*origin))
109 continue;
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";
118 return;
120 std::vector<int64> deletable_response_ids;
121 bool success = DeleteGroupAndRelatedRecords(database,
122 group->group_id,
123 &deletable_response_ids);
124 success = success && transaction.Commit();
125 DCHECK(success);
126 } // for each group
127 } // for each origin
130 } // namespace
132 // DatabaseTask -----------------------------------------
134 class AppCacheStorageImpl::DatabaseTask
135 : public base::RefCountedThreadSafe<DatabaseTask> {
136 public:
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.
149 void Schedule();
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();
167 protected:
168 friend class base::RefCountedThreadSafe<DatabaseTask>;
169 virtual ~DatabaseTask() {}
171 AppCacheStorageImpl* storage_;
172 AppCacheDatabase* database_;
173 DelegateReferenceVector delegates_;
175 private:
176 void CallRun(base::TimeTicks schedule_time);
177 void CallRunCompleted(base::TimeTicks schedule_time);
178 void OnFatalError();
180 scoped_refptr<base::MessageLoopProxy> io_thread_;
183 void AppCacheStorageImpl::DatabaseTask::Schedule() {
184 DCHECK(storage_);
185 DCHECK(io_thread_->BelongsToCurrentThread());
186 if (!storage_->database_)
187 return;
189 if (storage_->db_thread_->PostTask(
190 FROM_HERE,
191 base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) {
192 storage_->scheduled_database_tasks_.push_back(this);
193 } else {
194 NOTREACHED() << "Thread for database tasks is not running.";
198 void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
199 DCHECK(io_thread_->BelongsToCurrentThread());
200 delegates_.clear();
201 storage_ = NULL;
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();
210 Run();
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(
220 FROM_HERE,
221 base::Bind(&DatabaseTask::OnFatalError, this));
224 io_thread_->PostTask(
225 FROM_HERE,
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);
234 if (storage_) {
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();
239 RunCompleted();
240 AppCacheHistograms::AddCompletionRunTimeSample(
241 base::TimeTicks::Now() - run_time);
242 delegates_.clear();
246 void AppCacheStorageImpl::DatabaseTask::OnFatalError() {
247 if (storage_) {
248 DCHECK(io_thread_->BelongsToCurrentThread());
249 storage_->Disable();
250 storage_->DeleteAndStartOver();
254 // InitTask -------
256 class AppCacheStorageImpl::InitTask : public DatabaseTask {
257 public:
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_) {
263 db_file_path_ =
264 storage->cache_directory_.Append(kAppCacheDatabaseName);
265 disk_cache_directory_ =
266 storage->cache_directory_.Append(kDiskCacheDirectoryName);
270 // DatabaseTask:
271 virtual void Run() OVERRIDE;
272 virtual void RunCompleted() OVERRIDE;
274 protected:
275 virtual ~InitTask() {}
277 private:
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.
295 return;
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(
315 FROM_HERE,
316 base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
317 storage_->weak_factory_.GetWeakPtr()),
318 kDelay);
321 if (storage_->service()->quota_client())
322 storage_->service()->quota_client()->NotifyAppCacheReady();
325 // DisableDatabaseTask -------
327 class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
328 public:
329 explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
330 : DatabaseTask(storage) {}
332 // DatabaseTask:
333 virtual void Run() OVERRIDE { database_->Disable(); }
335 protected:
336 virtual ~DisableDatabaseTask() {}
339 // GetAllInfoTask -------
341 class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
342 public:
343 explicit GetAllInfoTask(AppCacheStorageImpl* storage)
344 : DatabaseTask(storage),
345 info_collection_(new AppCacheInfoCollection()) {
348 // DatabaseTask:
349 virtual void Run() OVERRIDE;
350 virtual void RunCompleted() OVERRIDE;
352 protected:
353 virtual ~GetAllInfoTask() {}
355 private:
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);
373 AppCacheInfo info;
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 {
395 protected:
396 explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
397 : DatabaseTask(storage) {}
398 virtual ~StoreOrLoadTask() {}
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(
416 int64 cache_id) {
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);
430 if (cache->get()) {
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);
442 return;
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);
454 if (group->get()) {
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);
463 } else {
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
479 // are in flight.
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 {
497 public:
498 CacheLoadTask(int64 cache_id, AppCacheStorageImpl* storage)
499 : StoreOrLoadTask(storage), cache_id_(cache_id),
500 success_(false) {}
502 // DatabaseTask:
503 virtual void Run() OVERRIDE;
504 virtual void RunCompleted() OVERRIDE;
506 protected:
507 virtual ~CacheLoadTask() {}
509 private:
510 int64 cache_id_;
511 bool success_;
514 void AppCacheStorageImpl::CacheLoadTask::Run() {
515 success_ =
516 database_->FindCache(cache_id_, &cache_record_) &&
517 database_->FindGroup(cache_record_.group_id, &group_record_) &&
518 FindRelatedCacheRecords(cache_id_);
520 if (success_)
521 database_->UpdateGroupLastAccessTime(group_record_.group_id,
522 base::Time::Now());
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 {
539 public:
540 GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
541 : StoreOrLoadTask(storage), manifest_url_(manifest_url),
542 success_(false) {}
544 // DatabaseTask:
545 virtual void Run() OVERRIDE;
546 virtual void RunCompleted() OVERRIDE;
548 protected:
549 virtual ~GroupLoadTask() {}
551 private:
552 GURL manifest_url_;
553 bool success_;
556 void AppCacheStorageImpl::GroupLoadTask::Run() {
557 success_ =
558 database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
559 database_->FindCacheForGroup(group_record_.group_id, &cache_record_) &&
560 FindRelatedCacheRecords(cache_record_.cache_id);
562 if (success_)
563 database_->UpdateGroupLastAccessTime(group_record_.group_id,
564 base::Time::Now());
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()) {
572 if (success_) {
573 DCHECK(group_record_.manifest_url == manifest_url_);
574 CreateCacheAndGroupFromRecords(&cache, &group);
575 } else {
576 group = storage_->working_set_.GetGroup(manifest_url_);
577 if (!group.get()) {
578 group =
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 {
589 public:
590 StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
591 AppCache* newest_cache);
593 void GetQuotaThenSchedule();
594 void OnQuotaCallback(storage::QuotaStatusCode status,
595 int64 usage,
596 int64 quota);
598 // DatabaseTask:
599 virtual void Run() OVERRIDE;
600 virtual void RunCompleted() OVERRIDE;
601 virtual void CancelCompletion() OVERRIDE;
603 protected:
604 virtual ~StoreGroupAndCacheTask() {}
606 private:
607 scoped_refptr<AppCacheGroup> group_;
608 scoped_refptr<AppCache> cache_;
609 bool success_;
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(
625 group,
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()) {
635 quota_manager =
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;
644 Schedule();
645 return;
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,
658 int64 usage,
659 int64 quota) {
660 if (storage_) {
661 if (status == storage::kQuotaStatusOk)
662 space_available_ = std::max(static_cast<int64>(0), quota - usage);
663 else
664 space_available_ = 0;
665 storage_->pending_quota_queries_.erase(this);
666 Schedule();
670 void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
671 DCHECK(!success_);
672 sql::Connection* connection = database_->db_connection();
673 if (!connection)
674 return;
676 sql::Transaction transaction(connection);
677 if (!transaction.Begin())
678 return;
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);
684 if (!success_) {
685 group_record_.creation_time = base::Time::Now();
686 group_record_.last_access_time = base::Time::Now();
687 success_ = database_->InsertGroup(&group_record_);
688 } else {
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,
694 base::Time::Now());
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);
708 ++entry_iter;
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);
715 ++id_iter;
718 success_ =
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
725 } else {
726 NOTREACHED() << "A existing group without a cache is unexpected";
730 success_ =
731 success_ &&
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_);
738 if (!success_)
739 return;
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();
746 return;
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;
753 success_ = false;
754 return;
756 success_ = transaction.Commit();
757 return;
760 // Check limits based on the space availbable given to us via the
761 // quota system.
762 int64 delta = new_origin_usage_ - old_origin_usage;
763 if (delta > space_available_) {
764 would_exceed_quota_ = true;
765 success_ = false;
766 return;
769 success_ = transaction.Commit();
772 void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
773 if (success_) {
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_);
784 FOR_EACH_DELEGATE(
785 delegates_,
786 OnGroupAndNewestCacheStored(
787 group_.get(), cache_.get(), success_, would_exceed_quota_));
788 group_ = NULL;
789 cache_ = NULL;
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();
799 group_ = NULL;
800 cache_ = NULL;
803 // FindMainResponseTask -------
805 // Helpers for FindMainResponseTask::Run()
806 namespace {
807 class SortByCachePreference
808 : public std::binary_function<
809 AppCacheDatabase::EntryRecord,
810 AppCacheDatabase::EntryRecord,
811 bool> {
812 public:
813 SortByCachePreference(int64 preferred_id, const std::set<int64>& in_use_ids)
814 : preferred_id_(preferred_id), in_use_ids_(in_use_ids) {
816 bool operator()(
817 const AppCacheDatabase::EntryRecord& lhs,
818 const AppCacheDatabase::EntryRecord& rhs) {
819 return compute_value(lhs) > compute_value(rhs);
821 private:
822 int compute_value(const AppCacheDatabase::EntryRecord& entry) {
823 if (entry.cache_id == preferred_id_)
824 return 100;
825 else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end())
826 return 50;
827 return 0;
829 int64 preferred_id_;
830 const std::set<int64>& in_use_ids_;
833 bool SortByLength(
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 {
841 public:
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()));
850 if (result.second)
851 GetOnlineWhiteListForCache(cache_id, &result.first->second);
852 return AppCache::FindNamespace(result.first->second, url) != NULL;
855 private:
856 void GetOnlineWhiteListForCache(
857 int64 cache_id, AppCacheNamespaceVector* namespaces) {
858 DCHECK(namespaces && namespaces->empty());
859 typedef std::vector<AppCacheDatabase::OnlineWhiteListRecord>
860 WhiteListVector;
861 WhiteListVector records;
862 if (!database_->FindOnlineWhiteListForCache(cache_id, &records))
863 return;
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));
869 ++iter;
873 // Key is cache id
874 typedef std::map<int64, AppCacheNamespaceVector> WhiteListMap;
875 WhiteListMap namespaces_map_;
876 AppCacheDatabase* database_;
879 } // namespace
881 class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
882 public:
883 FindMainResponseTask(AppCacheStorageImpl* storage,
884 const GURL& url,
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) {
890 if (groups_in_use) {
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)
897 continue;
898 cache_ids_in_use_.insert(cache->cache_id());
903 // DatabaseTask:
904 virtual void Run() OVERRIDE;
905 virtual void RunCompleted() OVERRIDE;
907 protected:
908 virtual ~FindMainResponseTask() {}
910 private:
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);
922 GURL url_;
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_;
928 int64 cache_id_;
929 int64 group_id_;
930 GURL manifest_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
936 // to be refined.
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() &&
962 group_id_ != 0);
963 return;
966 // We didn't find anything.
967 DCHECK(cache_id_ == kAppCacheNoCacheId && manifest_url_.is_empty() &&
968 group_id_ == 0);
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)) {
986 continue;
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.
995 return false;
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())) {
1005 return false;
1008 NetworkNamespaceHelper network_namespace_helper(database_);
1009 if (FindNamespaceHelper(preferred_cache_id,
1010 &all_intercepts,
1011 &network_namespace_helper) ||
1012 FindNamespaceHelper(preferred_cache_id,
1013 &all_fallbacks,
1014 &network_namespace_helper)) {
1015 return true;
1017 return false;
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
1026 // precedence.
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_))
1036 continue;
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))
1041 continue;
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));
1048 else
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.
1058 return false;
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,
1069 &entry_record)) {
1070 AppCacheDatabase::GroupRecord group_record;
1071 if ((entry_record.flags & AppCacheEntry::FOREIGN) ||
1072 !database_->FindGroupForCache(entry_record.cache_id, &group_record)) {
1073 continue;
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);
1082 else
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 {
1099 public:
1100 MarkEntryAsForeignTask(
1101 AppCacheStorageImpl* storage, const GURL& url, int64 cache_id)
1102 : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}
1104 // DatabaseTask:
1105 virtual void Run() OVERRIDE;
1106 virtual void RunCompleted() OVERRIDE;
1108 protected:
1109 virtual ~MarkEntryAsForeignTask() {}
1111 private:
1112 int64 cache_id_;
1113 GURL entry_url_;
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 {
1129 public:
1130 MakeGroupObsoleteTask(AppCacheStorageImpl* storage,
1131 AppCacheGroup* group,
1132 int response_code);
1134 // DatabaseTask:
1135 virtual void Run() OVERRIDE;
1136 virtual void RunCompleted() OVERRIDE;
1137 virtual void CancelCompletion() OVERRIDE;
1139 protected:
1140 virtual ~MakeGroupObsoleteTask() {}
1142 private:
1143 scoped_refptr<AppCacheGroup> group_;
1144 int64 group_id_;
1145 GURL origin_;
1146 bool success_;
1147 int response_code_;
1148 int64 new_origin_usage_;
1149 std::vector<int64> newly_deletable_response_ids_;
1152 AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
1153 AppCacheStorageImpl* storage,
1154 AppCacheGroup* group,
1155 int response_code)
1156 : DatabaseTask(storage),
1157 group_(group),
1158 group_id_(group->group_id()),
1159 origin_(group->manifest_url().GetOrigin()),
1160 success_(false),
1161 response_code_(response_code),
1162 new_origin_usage_(-1) {}
1164 void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
1165 DCHECK(!success_);
1166 sql::Connection* connection = database_->db_connection();
1167 if (!connection)
1168 return;
1170 sql::Transaction transaction(connection);
1171 if (!transaction.Begin())
1172 return;
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_);
1178 success_ = true;
1179 return;
1182 DCHECK_EQ(group_record.origin, origin_);
1183 success_ = DeleteGroupAndRelatedRecords(database_,
1184 group_id_,
1185 &newly_deletable_response_ids_);
1187 new_origin_usage_ = database_->GetOriginUsage(origin_);
1188 success_ = success_ && transaction.Commit();
1191 void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
1192 if (success_) {
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());
1204 FOR_EACH_DELEGATE(
1205 delegates_, OnGroupMadeObsolete(group_.get(), success_, response_code_));
1206 group_ = NULL;
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();
1213 group_ = NULL;
1216 // GetDeletableResponseIdsTask -------
1218 class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask {
1219 public:
1220 GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64 max_rowid)
1221 : DatabaseTask(storage), max_rowid_(max_rowid) {}
1223 // DatabaseTask:
1224 virtual void Run() OVERRIDE;
1225 virtual void RunCompleted() OVERRIDE;
1227 protected:
1228 virtual ~GetDeletableResponseIdsTask() {}
1230 private:
1231 int64 max_rowid_;
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 {
1250 public:
1251 explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1252 : DatabaseTask(storage) {}
1254 // DatabaseTask:
1255 virtual void Run() OVERRIDE;
1257 std::vector<int64> response_ids_;
1259 protected:
1260 virtual ~InsertDeletableResponseIdsTask() {}
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 {
1272 public:
1273 explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1274 : DatabaseTask(storage) {}
1276 // DatabaseTask:
1277 virtual void Run() OVERRIDE;
1279 std::vector<int64> response_ids_;
1281 protected:
1282 virtual ~DeleteDeletableResponseIdsTask() {}
1285 void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
1286 database_->DeleteDeletableResponseIds(response_ids_);
1289 // UpdateGroupLastAccessTimeTask -------
1291 class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask
1292 : public DatabaseTask {
1293 public:
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());
1301 // DatabaseTask:
1302 virtual void Run() OVERRIDE;
1304 protected:
1305 virtual ~UpdateGroupLastAccessTimeTask() {}
1307 private:
1308 int64 group_id_;
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),
1325 database_(NULL),
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));
1338 if (database_ &&
1339 !db_thread_->PostTask(
1340 FROM_HERE,
1341 base::Bind(&ClearSessionOnlyOrigins,
1342 database_,
1343 make_scoped_refptr(service_->special_storage_policy()),
1344 service()->force_keep_session_state()))) {
1345 delete database_;
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;
1360 if (!is_incognito_)
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));
1368 task->Schedule();
1371 void AppCacheStorageImpl::Disable() {
1372 if (is_disabled_)
1373 return;
1374 VLOG(1) << "Disabling appcache storage.";
1375 is_disabled_ = true;
1376 ClearUsageMapAndNotify();
1377 working_set()->Disable();
1378 if (disk_cache_)
1379 disk_cache_->Disable();
1380 scoped_refptr<DisableDatabaseTask> task(new DisableDatabaseTask(this));
1381 task->Schedule();
1384 void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) {
1385 DCHECK(delegate);
1386 scoped_refptr<GetAllInfoTask> task(new GetAllInfoTask(this));
1387 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1388 task->Schedule();
1391 void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) {
1392 DCHECK(delegate);
1393 if (is_disabled_) {
1394 delegate->OnCacheLoaded(NULL, id);
1395 return;
1398 AppCache* cache = working_set_.GetCache(id);
1399 if (cache) {
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();
1407 return;
1409 scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id));
1410 if (task.get()) {
1411 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1412 return;
1414 task = new CacheLoadTask(id, this);
1415 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1416 task->Schedule();
1417 pending_cache_loads_[id] = task.get();
1420 void AppCacheStorageImpl::LoadOrCreateGroup(
1421 const GURL& manifest_url, Delegate* delegate) {
1422 DCHECK(delegate);
1423 if (is_disabled_) {
1424 delegate->OnGroupLoaded(NULL, manifest_url);
1425 return;
1428 AppCacheGroup* group = working_set_.GetGroup(manifest_url);
1429 if (group) {
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();
1435 return;
1438 scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url));
1439 if (task.get()) {
1440 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1441 return;
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);
1449 return;
1452 task = new GroupLoadTask(manifest_url, this);
1453 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1454 task->Schedule();
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) {
1481 DCHECK(delegate);
1483 const GURL* url_ptr = &url;
1484 GURL url_no_ref;
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
1495 // the database.
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)) {
1505 return;
1507 } else {
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)) {
1513 return;
1519 if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) {
1520 // No need to query the database, return async'ly but without going thru
1521 // the DB thread.
1522 scoped_refptr<AppCacheGroup> no_group;
1523 scoped_refptr<AppCache> no_cache;
1524 ScheduleSimpleTask(
1525 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1526 weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group,
1527 no_cache,
1528 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
1529 return;
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,
1535 groups_in_use));
1536 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1537 task->Schedule();
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)
1544 return false;
1546 AppCacheEntry* entry = cache->GetEntry(url);
1547 if (!entry || entry->IsForeign())
1548 return false;
1550 ScheduleSimpleTask(
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))));
1555 return true;
1558 void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
1559 const GURL& url,
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) {
1580 FOR_EACH_DELEGATE(
1581 (*delegates),
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;
1600 return;
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);
1614 if (cache) {
1615 AppCacheEntry* entry = cache->GetEntry(entry_url);
1616 DCHECK(entry);
1617 if (entry)
1618 entry->add_types(AppCacheEntry::FOREIGN);
1620 scoped_refptr<MarkEntryAsForeignTask> task(
1621 new MarkEntryAsForeignTask(this, entry_url, cache_id));
1622 task->Schedule();
1623 pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
1626 void AppCacheStorageImpl::MakeGroupObsolete(AppCacheGroup* group,
1627 Delegate* delegate,
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));
1633 task->Schedule();
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())
1649 return;
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;
1662 task->Schedule();
1665 void AppCacheStorageImpl::DeleteResponses(
1666 const GURL& manifest_url, const std::vector<int64>& response_ids) {
1667 if (response_ids.empty())
1668 return;
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_));
1677 task->Schedule();
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(
1696 FROM_HERE,
1697 base::Bind(&AppCacheStorageImpl::DeleteOneResponse,
1698 weak_factory_.GetWeakPtr()),
1699 kDelay);
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;
1712 return;
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;
1726 if (is_disabled_)
1727 return;
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_);
1740 task->Schedule();
1743 if (deletable_response_ids_.empty()) {
1744 scoped_refptr<GetDeletableResponseIdsTask> task(
1745 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
1746 task->Schedule();
1747 return;
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;
1758 return NULL;
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;
1766 return NULL;
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);
1775 ++iter;
1779 void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure& task) {
1780 pending_simple_tasks_.push_back(task);
1781 base::MessageLoop::current()->PostTask(
1782 FROM_HERE,
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();
1791 task.Run();
1794 AppCacheDiskCache* AppCacheStorageImpl::disk_cache() {
1795 DCHECK(IsInitTaskComplete());
1797 if (is_disabled_)
1798 return NULL;
1800 if (!disk_cache_) {
1801 int rv = net::OK;
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)));
1808 } else {
1809 rv = disk_cache_->InitWithDiskBackend(
1810 cache_directory_.Append(kDiskCacheDirectoryName),
1811 kMaxDiskCacheSize,
1812 false,
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.
1832 Disable();
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(
1846 FROM_HERE,
1847 base::Bind(&base::DoNothing),
1848 base::Bind(&AppCacheStorageImpl::DeleteAndStartOverPart2,
1849 weak_factory_.GetWeakPtr()));
1853 void AppCacheStorageImpl::DeleteAndStartOverPart2() {
1854 db_thread_->PostTaskAndReply(
1855 FROM_HERE,
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