When Retrier succeeds, record errors it encountered.
[chromium-blink-merge.git] / webkit / appcache / appcache_storage_impl.cc
blob6d9add0fbb2efdb74f69fde00b8179b73a18780d
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "webkit/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.h"
17 #include "base/stl_util.h"
18 #include "base/string_util.h"
19 #include "net/base/cache_type.h"
20 #include "net/base/net_errors.h"
21 #include "sql/connection.h"
22 #include "sql/transaction.h"
23 #include "webkit/appcache/appcache.h"
24 #include "webkit/appcache/appcache_database.h"
25 #include "webkit/appcache/appcache_entry.h"
26 #include "webkit/appcache/appcache_group.h"
27 #include "webkit/appcache/appcache_histograms.h"
28 #include "webkit/appcache/appcache_quota_client.h"
29 #include "webkit/appcache/appcache_response.h"
30 #include "webkit/appcache/appcache_service.h"
31 #include "webkit/quota/quota_client.h"
32 #include "webkit/quota/quota_manager.h"
33 #include "webkit/quota/special_storage_policy.h"
35 namespace appcache {
37 // Hard coded default when not using quota management.
38 static const int kDefaultQuota = 5 * 1024 * 1024;
40 static const int kMaxDiskCacheSize = 250 * 1024 * 1024;
41 static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024;
42 static const base::FilePath::CharType kDiskCacheDirectoryName[] =
43 FILE_PATH_LITERAL("Cache");
45 namespace {
47 // Helpers for clearing data from the AppCacheDatabase.
48 bool DeleteGroupAndRelatedRecords(AppCacheDatabase* database,
49 int64 group_id,
50 std::vector<int64>* deletable_response_ids) {
51 AppCacheDatabase::CacheRecord cache_record;
52 bool success = false;
53 if (database->FindCacheForGroup(group_id, &cache_record)) {
54 database->FindResponseIdsForCacheAsVector(cache_record.cache_id,
55 deletable_response_ids);
56 success =
57 database->DeleteGroup(group_id) &&
58 database->DeleteCache(cache_record.cache_id) &&
59 database->DeleteEntriesForCache(cache_record.cache_id) &&
60 database->DeleteNamespacesForCache(cache_record.cache_id) &&
61 database->DeleteOnlineWhiteListForCache(cache_record.cache_id) &&
62 database->InsertDeletableResponseIds(*deletable_response_ids);
63 } else {
64 NOTREACHED() << "A existing group without a cache is unexpected";
65 success = database->DeleteGroup(group_id);
67 return success;
70 // Destroys |database|. If there is appcache data to be deleted
71 // (|force_keep_session_state| is false), deletes session-only appcache data.
72 void ClearSessionOnlyOrigins(
73 AppCacheDatabase* database,
74 scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy,
75 bool force_keep_session_state) {
76 scoped_ptr<AppCacheDatabase> database_to_delete(database);
78 // If saving session state, only delete the database.
79 if (force_keep_session_state)
80 return;
82 bool has_session_only_appcaches =
83 special_storage_policy.get() &&
84 special_storage_policy->HasSessionOnlyOrigins();
86 // Clearning only session-only databases, and there are none.
87 if (!has_session_only_appcaches)
88 return;
90 std::set<GURL> origins;
91 database->FindOriginsWithGroups(&origins);
92 if (origins.empty())
93 return; // nothing to delete
95 sql::Connection* connection = database->db_connection();
96 if (!connection) {
97 NOTREACHED() << "Missing database connection.";
98 return;
101 std::set<GURL>::const_iterator origin;
102 for (origin = origins.begin(); origin != origins.end(); ++origin) {
103 if (!special_storage_policy->IsStorageSessionOnly(*origin))
104 continue;
105 if (special_storage_policy &&
106 special_storage_policy->IsStorageProtected(*origin))
107 continue;
109 std::vector<AppCacheDatabase::GroupRecord> groups;
110 database->FindGroupsForOrigin(*origin, &groups);
111 std::vector<AppCacheDatabase::GroupRecord>::const_iterator group;
112 for (group = groups.begin(); group != groups.end(); ++group) {
113 sql::Transaction transaction(connection);
114 if (!transaction.Begin()) {
115 NOTREACHED() << "Failed to start transaction";
116 return;
118 std::vector<int64> deletable_response_ids;
119 bool success = DeleteGroupAndRelatedRecords(database,
120 group->group_id,
121 &deletable_response_ids);
122 success = success && transaction.Commit();
123 DCHECK(success);
124 } // for each group
125 } // for each origin
128 } // namespace
130 // DatabaseTask -----------------------------------------
132 class AppCacheStorageImpl::DatabaseTask
133 : public base::RefCountedThreadSafe<DatabaseTask> {
134 public:
135 explicit DatabaseTask(AppCacheStorageImpl* storage)
136 : storage_(storage), database_(storage->database_),
137 io_thread_(base::MessageLoopProxy::current()) {
138 DCHECK(io_thread_);
141 void AddDelegate(DelegateReference* delegate_reference) {
142 delegates_.push_back(make_scoped_refptr(delegate_reference));
145 // Schedules a task to be Run() on the DB thread. Tasks
146 // are run in the order in which they are scheduled.
147 void Schedule();
149 // Called on the DB thread.
150 virtual void Run() = 0;
152 // Called on the IO thread after Run() has completed.
153 virtual void RunCompleted() {}
155 // Once scheduled a task cannot be cancelled, but the
156 // call to RunCompleted may be. This method should only be
157 // called on the IO thread. This is used by AppCacheStorageImpl
158 // to cancel the completion calls when AppCacheStorageImpl is
159 // destructed. This method may be overriden to release or delete
160 // additional data associated with the task that is not DB thread
161 // safe. If overriden, this base class method must be called from
162 // within the override.
163 virtual void CancelCompletion();
165 protected:
166 friend class base::RefCountedThreadSafe<DatabaseTask>;
167 virtual ~DatabaseTask() {}
169 AppCacheStorageImpl* storage_;
170 AppCacheDatabase* database_;
171 DelegateReferenceVector delegates_;
173 private:
174 void CallRun(base::TimeTicks schedule_time);
175 void CallRunCompleted(base::TimeTicks schedule_time);
176 void CallDisableStorage();
178 scoped_refptr<base::MessageLoopProxy> io_thread_;
181 void AppCacheStorageImpl::DatabaseTask::Schedule() {
182 DCHECK(storage_);
183 DCHECK(io_thread_->BelongsToCurrentThread());
184 if (storage_->db_thread_->PostTask(
185 FROM_HERE,
186 base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) {
187 storage_->scheduled_database_tasks_.push_back(this);
188 } else {
189 NOTREACHED() << "The database thread is not running.";
193 void AppCacheStorageImpl::DatabaseTask::CancelCompletion() {
194 DCHECK(io_thread_->BelongsToCurrentThread());
195 delegates_.clear();
196 storage_ = NULL;
199 void AppCacheStorageImpl::DatabaseTask::CallRun(
200 base::TimeTicks schedule_time) {
201 AppCacheHistograms::AddTaskQueueTimeSample(
202 base::TimeTicks::Now() - schedule_time);
203 if (!database_->is_disabled()) {
204 base::TimeTicks run_time = base::TimeTicks::Now();
205 Run();
206 AppCacheHistograms::AddTaskRunTimeSample(
207 base::TimeTicks::Now() - run_time);
208 if (database_->is_disabled()) {
209 io_thread_->PostTask(
210 FROM_HERE,
211 base::Bind(&DatabaseTask::CallDisableStorage, this));
214 io_thread_->PostTask(
215 FROM_HERE,
216 base::Bind(&DatabaseTask::CallRunCompleted, this,
217 base::TimeTicks::Now()));
220 void AppCacheStorageImpl::DatabaseTask::CallRunCompleted(
221 base::TimeTicks schedule_time) {
222 AppCacheHistograms::AddCompletionQueueTimeSample(
223 base::TimeTicks::Now() - schedule_time);
224 if (storage_) {
225 DCHECK(io_thread_->BelongsToCurrentThread());
226 DCHECK(storage_->scheduled_database_tasks_.front() == this);
227 storage_->scheduled_database_tasks_.pop_front();
228 base::TimeTicks run_time = base::TimeTicks::Now();
229 RunCompleted();
230 AppCacheHistograms::AddCompletionRunTimeSample(
231 base::TimeTicks::Now() - run_time);
232 delegates_.clear();
236 void AppCacheStorageImpl::DatabaseTask::CallDisableStorage() {
237 if (storage_) {
238 DCHECK(io_thread_->BelongsToCurrentThread());
239 storage_->Disable();
243 // InitTask -------
245 class AppCacheStorageImpl::InitTask : public DatabaseTask {
246 public:
247 explicit InitTask(AppCacheStorageImpl* storage)
248 : DatabaseTask(storage), last_group_id_(0),
249 last_cache_id_(0), last_response_id_(0),
250 last_deletable_response_rowid_(0) {}
252 // DatabaseTask:
253 virtual void Run() OVERRIDE;
254 virtual void RunCompleted() OVERRIDE;
256 protected:
257 virtual ~InitTask() {}
259 private:
260 int64 last_group_id_;
261 int64 last_cache_id_;
262 int64 last_response_id_;
263 int64 last_deletable_response_rowid_;
264 std::map<GURL, int64> usage_map_;
267 void AppCacheStorageImpl::InitTask::Run() {
268 database_->FindLastStorageIds(
269 &last_group_id_, &last_cache_id_, &last_response_id_,
270 &last_deletable_response_rowid_);
271 database_->GetAllOriginUsage(&usage_map_);
274 void AppCacheStorageImpl::InitTask::RunCompleted() {
275 storage_->last_group_id_ = last_group_id_;
276 storage_->last_cache_id_ = last_cache_id_;
277 storage_->last_response_id_ = last_response_id_;
278 storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_;
280 if (!storage_->is_disabled()) {
281 storage_->usage_map_.swap(usage_map_);
282 const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5);
283 base::MessageLoop::current()->PostDelayedTask(
284 FROM_HERE,
285 base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses,
286 storage_->weak_factory_.GetWeakPtr()),
287 kDelay);
290 if (storage_->service()->quota_client())
291 storage_->service()->quota_client()->NotifyAppCacheReady();
294 // CloseConnectionTask -------
296 class AppCacheStorageImpl::CloseConnectionTask : public DatabaseTask {
297 public:
298 explicit CloseConnectionTask(AppCacheStorageImpl* storage)
299 : DatabaseTask(storage) {}
301 // DatabaseTask:
302 virtual void Run() OVERRIDE { database_->CloseConnection(); }
304 protected:
305 virtual ~CloseConnectionTask() {}
308 // DisableDatabaseTask -------
310 class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask {
311 public:
312 explicit DisableDatabaseTask(AppCacheStorageImpl* storage)
313 : DatabaseTask(storage) {}
315 // DatabaseTask:
316 virtual void Run() OVERRIDE { database_->Disable(); }
318 protected:
319 virtual ~DisableDatabaseTask() {}
322 // GetAllInfoTask -------
324 class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask {
325 public:
326 explicit GetAllInfoTask(AppCacheStorageImpl* storage)
327 : DatabaseTask(storage),
328 info_collection_(new AppCacheInfoCollection()) {
331 // DatabaseTask:
332 virtual void Run() OVERRIDE;
333 virtual void RunCompleted() OVERRIDE;
335 protected:
336 virtual ~GetAllInfoTask() {}
338 private:
339 scoped_refptr<AppCacheInfoCollection> info_collection_;
342 void AppCacheStorageImpl::GetAllInfoTask::Run() {
343 std::set<GURL> origins;
344 database_->FindOriginsWithGroups(&origins);
345 for (std::set<GURL>::const_iterator origin = origins.begin();
346 origin != origins.end(); ++origin) {
347 AppCacheInfoVector& infos =
348 info_collection_->infos_by_origin[*origin];
349 std::vector<AppCacheDatabase::GroupRecord> groups;
350 database_->FindGroupsForOrigin(*origin, &groups);
351 for (std::vector<AppCacheDatabase::GroupRecord>::const_iterator
352 group = groups.begin();
353 group != groups.end(); ++group) {
354 AppCacheDatabase::CacheRecord cache_record;
355 database_->FindCacheForGroup(group->group_id, &cache_record);
356 AppCacheInfo info;
357 info.manifest_url = group->manifest_url;
358 info.creation_time = group->creation_time;
359 info.size = cache_record.cache_size;
360 info.last_access_time = group->last_access_time;
361 info.last_update_time = cache_record.update_time;
362 info.cache_id = cache_record.cache_id;
363 info.group_id = group->group_id;
364 info.is_complete = true;
365 infos.push_back(info);
370 void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() {
371 DCHECK(delegates_.size() == 1);
372 FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_));
375 // StoreOrLoadTask -------
377 class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask {
378 protected:
379 explicit StoreOrLoadTask(AppCacheStorageImpl* storage)
380 : DatabaseTask(storage) {}
381 virtual ~StoreOrLoadTask() {}
383 bool FindRelatedCacheRecords(int64 cache_id);
384 void CreateCacheAndGroupFromRecords(
385 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group);
387 AppCacheDatabase::GroupRecord group_record_;
388 AppCacheDatabase::CacheRecord cache_record_;
389 std::vector<AppCacheDatabase::EntryRecord> entry_records_;
390 std::vector<AppCacheDatabase::NamespaceRecord>
391 intercept_namespace_records_;
392 std::vector<AppCacheDatabase::NamespaceRecord>
393 fallback_namespace_records_;
394 std::vector<AppCacheDatabase::OnlineWhiteListRecord>
395 online_whitelist_records_;
398 bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords(
399 int64 cache_id) {
400 return database_->FindEntriesForCache(cache_id, &entry_records_) &&
401 database_->FindNamespacesForCache(
402 cache_id, &intercept_namespace_records_,
403 &fallback_namespace_records_) &&
404 database_->FindOnlineWhiteListForCache(
405 cache_id, &online_whitelist_records_);
408 void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords(
409 scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) {
410 DCHECK(storage_ && cache && group);
412 (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id);
413 if (cache->get()) {
414 (*group) = cache->get()->owning_group();
415 DCHECK(group->get());
416 DCHECK_EQ(group_record_.group_id, group->get()->group_id());
418 // TODO(michaeln): histogram is fishing for clues to crbug/95101
419 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
420 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
421 AppCacheHistograms::CALLSITE_0);
424 storage_->NotifyStorageAccessed(group_record_.origin);
425 return;
428 (*cache) = new AppCache(storage_, cache_record_.cache_id);
429 cache->get()->InitializeWithDatabaseRecords(
430 cache_record_, entry_records_,
431 intercept_namespace_records_,
432 fallback_namespace_records_,
433 online_whitelist_records_);
434 cache->get()->set_complete(true);
436 (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url);
437 if (group->get()) {
438 DCHECK(group_record_.group_id == group->get()->group_id());
439 group->get()->AddCache(cache->get());
441 // TODO(michaeln): histogram is fishing for clues to crbug/95101
442 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
443 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
444 AppCacheHistograms::CALLSITE_1);
446 } else {
447 (*group) = new AppCacheGroup(
448 storage_, group_record_.manifest_url,
449 group_record_.group_id);
450 group->get()->set_creation_time(group_record_.creation_time);
451 group->get()->AddCache(cache->get());
453 // TODO(michaeln): histogram is fishing for clues to crbug/95101
454 if (!cache->get()->GetEntry(group_record_.manifest_url)) {
455 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
456 AppCacheHistograms::CALLSITE_2);
459 DCHECK(group->get()->newest_complete_cache() == cache->get());
461 // We have to update foriegn entries if MarkEntryAsForeignTasks
462 // are in flight.
463 std::vector<GURL> urls;
464 storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls);
465 for (std::vector<GURL>::iterator iter = urls.begin();
466 iter != urls.end(); ++iter) {
467 DCHECK(cache->get()->GetEntry(*iter));
468 cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN);
471 storage_->NotifyStorageAccessed(group_record_.origin);
473 // TODO(michaeln): Maybe verify that the responses we expect to exist
474 // do actually exist in the disk_cache (and if not then what?)
477 // CacheLoadTask -------
479 class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask {
480 public:
481 CacheLoadTask(int64 cache_id, AppCacheStorageImpl* storage)
482 : StoreOrLoadTask(storage), cache_id_(cache_id),
483 success_(false) {}
485 // DatabaseTask:
486 virtual void Run() OVERRIDE;
487 virtual void RunCompleted() OVERRIDE;
489 protected:
490 virtual ~CacheLoadTask() {}
492 private:
493 int64 cache_id_;
494 bool success_;
497 void AppCacheStorageImpl::CacheLoadTask::Run() {
498 success_ =
499 database_->FindCache(cache_id_, &cache_record_) &&
500 database_->FindGroup(cache_record_.group_id, &group_record_) &&
501 FindRelatedCacheRecords(cache_id_);
503 if (success_)
504 database_->UpdateGroupLastAccessTime(group_record_.group_id,
505 base::Time::Now());
508 void AppCacheStorageImpl::CacheLoadTask::RunCompleted() {
509 storage_->pending_cache_loads_.erase(cache_id_);
510 scoped_refptr<AppCache> cache;
511 scoped_refptr<AppCacheGroup> group;
512 if (success_ && !storage_->is_disabled()) {
513 DCHECK(cache_record_.cache_id == cache_id_);
514 CreateCacheAndGroupFromRecords(&cache, &group);
516 FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache, cache_id_));
519 // GroupLoadTask -------
521 class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask {
522 public:
523 GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage)
524 : StoreOrLoadTask(storage), manifest_url_(manifest_url),
525 success_(false) {}
527 // DatabaseTask:
528 virtual void Run() OVERRIDE;
529 virtual void RunCompleted() OVERRIDE;
531 protected:
532 virtual ~GroupLoadTask() {}
534 private:
535 GURL manifest_url_;
536 bool success_;
539 void AppCacheStorageImpl::GroupLoadTask::Run() {
540 success_ =
541 database_->FindGroupForManifestUrl(manifest_url_, &group_record_) &&
542 database_->FindCacheForGroup(group_record_.group_id, &cache_record_) &&
543 FindRelatedCacheRecords(cache_record_.cache_id);
545 if (success_)
546 database_->UpdateGroupLastAccessTime(group_record_.group_id,
547 base::Time::Now());
550 void AppCacheStorageImpl::GroupLoadTask::RunCompleted() {
551 storage_->pending_group_loads_.erase(manifest_url_);
552 scoped_refptr<AppCacheGroup> group;
553 scoped_refptr<AppCache> cache;
554 if (!storage_->is_disabled()) {
555 if (success_) {
556 DCHECK(group_record_.manifest_url == manifest_url_);
557 CreateCacheAndGroupFromRecords(&cache, &group);
558 } else {
559 group = storage_->working_set_.GetGroup(manifest_url_);
560 if (!group) {
561 group = new AppCacheGroup(
562 storage_, manifest_url_, storage_->NewGroupId());
566 FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group, manifest_url_));
569 // StoreGroupAndCacheTask -------
571 class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask {
572 public:
573 StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group,
574 AppCache* newest_cache);
576 void GetQuotaThenSchedule();
577 void OnQuotaCallback(
578 quota::QuotaStatusCode status, int64 usage, int64 quota);
580 // DatabaseTask:
581 virtual void Run() OVERRIDE;
582 virtual void RunCompleted() OVERRIDE;
583 virtual void CancelCompletion() OVERRIDE;
585 protected:
586 virtual ~StoreGroupAndCacheTask() {}
588 private:
589 scoped_refptr<AppCacheGroup> group_;
590 scoped_refptr<AppCache> cache_;
591 bool success_;
592 bool would_exceed_quota_;
593 int64 space_available_;
594 int64 new_origin_usage_;
595 std::vector<int64> newly_deletable_response_ids_;
598 AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask(
599 AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache)
600 : StoreOrLoadTask(storage), group_(group), cache_(newest_cache),
601 success_(false), would_exceed_quota_(false),
602 space_available_(-1), new_origin_usage_(-1) {
603 group_record_.group_id = group->group_id();
604 group_record_.manifest_url = group->manifest_url();
605 group_record_.origin = group_record_.manifest_url.GetOrigin();
606 newest_cache->ToDatabaseRecords(
607 group,
608 &cache_record_, &entry_records_,
609 &intercept_namespace_records_,
610 &fallback_namespace_records_,
611 &online_whitelist_records_);
614 void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() {
615 quota::QuotaManager* quota_manager = NULL;
616 if (storage_->service()->quota_manager_proxy()) {
617 quota_manager =
618 storage_->service()->quota_manager_proxy()->quota_manager();
621 if (!quota_manager) {
622 if (storage_->service()->special_storage_policy() &&
623 storage_->service()->special_storage_policy()->IsStorageUnlimited(
624 group_record_.origin))
625 space_available_ = kint64max;
626 Schedule();
627 return;
630 // We have to ask the quota manager for the value.
631 storage_->pending_quota_queries_.insert(this);
632 quota_manager->GetUsageAndQuota(
633 group_record_.origin, quota::kStorageTypeTemporary,
634 base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this));
637 void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback(
638 quota::QuotaStatusCode status, int64 usage, int64 quota) {
639 if (storage_) {
640 if (status == quota::kQuotaStatusOk)
641 space_available_ = std::max(static_cast<int64>(0), quota - usage);
642 else
643 space_available_ = 0;
644 storage_->pending_quota_queries_.erase(this);
645 Schedule();
649 void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() {
650 DCHECK(!success_);
651 sql::Connection* connection = database_->db_connection();
652 if (!connection)
653 return;
655 sql::Transaction transaction(connection);
656 if (!transaction.Begin())
657 return;
659 int64 old_origin_usage = database_->GetOriginUsage(group_record_.origin);
661 AppCacheDatabase::GroupRecord existing_group;
662 success_ = database_->FindGroup(group_record_.group_id, &existing_group);
663 if (!success_) {
664 group_record_.creation_time = base::Time::Now();
665 group_record_.last_access_time = base::Time::Now();
666 success_ = database_->InsertGroup(&group_record_);
667 } else {
668 DCHECK(group_record_.group_id == existing_group.group_id);
669 DCHECK(group_record_.manifest_url == existing_group.manifest_url);
670 DCHECK(group_record_.origin == existing_group.origin);
672 database_->UpdateGroupLastAccessTime(group_record_.group_id,
673 base::Time::Now());
675 AppCacheDatabase::CacheRecord cache;
676 if (database_->FindCacheForGroup(group_record_.group_id, &cache)) {
677 // Get the set of response ids in the old cache.
678 std::set<int64> existing_response_ids;
679 database_->FindResponseIdsForCacheAsSet(cache.cache_id,
680 &existing_response_ids);
682 // Remove those that remain in the new cache.
683 std::vector<AppCacheDatabase::EntryRecord>::const_iterator entry_iter =
684 entry_records_.begin();
685 while (entry_iter != entry_records_.end()) {
686 existing_response_ids.erase(entry_iter->response_id);
687 ++entry_iter;
690 // The rest are deletable.
691 std::set<int64>::const_iterator id_iter = existing_response_ids.begin();
692 while (id_iter != existing_response_ids.end()) {
693 newly_deletable_response_ids_.push_back(*id_iter);
694 ++id_iter;
697 success_ =
698 database_->DeleteCache(cache.cache_id) &&
699 database_->DeleteEntriesForCache(cache.cache_id) &&
700 database_->DeleteNamespacesForCache(cache.cache_id) &&
701 database_->DeleteOnlineWhiteListForCache(cache.cache_id) &&
702 database_->InsertDeletableResponseIds(newly_deletable_response_ids_);
703 // TODO(michaeln): store group_id too with deletable ids
704 } else {
705 NOTREACHED() << "A existing group without a cache is unexpected";
709 success_ =
710 success_ &&
711 database_->InsertCache(&cache_record_) &&
712 database_->InsertEntryRecords(entry_records_) &&
713 database_->InsertNamespaceRecords(intercept_namespace_records_) &&
714 database_->InsertNamespaceRecords(fallback_namespace_records_) &&
715 database_->InsertOnlineWhiteListRecords(online_whitelist_records_);
717 if (!success_)
718 return;
720 new_origin_usage_ = database_->GetOriginUsage(group_record_.origin);
722 // Only check quota when the new usage exceeds the old usage.
723 if (new_origin_usage_ <= old_origin_usage) {
724 success_ = transaction.Commit();
725 return;
728 // Use a simple hard-coded value when not using quota management.
729 if (space_available_ == -1) {
730 if (new_origin_usage_ > kDefaultQuota) {
731 would_exceed_quota_ = true;
732 success_ = false;
733 return;
735 success_ = transaction.Commit();
736 return;
739 // Check limits based on the space availbable given to us via the
740 // quota system.
741 int64 delta = new_origin_usage_ - old_origin_usage;
742 if (delta > space_available_) {
743 would_exceed_quota_ = true;
744 success_ = false;
745 return;
748 success_ = transaction.Commit();
751 void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() {
752 if (success_) {
753 storage_->UpdateUsageMapAndNotify(
754 group_->manifest_url().GetOrigin(), new_origin_usage_);
755 if (cache_ != group_->newest_complete_cache()) {
756 cache_->set_complete(true);
757 group_->AddCache(cache_);
759 if (group_->creation_time().is_null())
760 group_->set_creation_time(group_record_.creation_time);
761 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
763 FOR_EACH_DELEGATE(delegates_,
764 OnGroupAndNewestCacheStored(group_, cache_, success_,
765 would_exceed_quota_));
766 group_ = NULL;
767 cache_ = NULL;
769 // TODO(michaeln): if (would_exceed_quota_) what if the current usage
770 // also exceeds the quota? http://crbug.com/83968
773 void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() {
774 // Overriden to safely drop our reference to the group and cache
775 // which are not thread safe refcounted.
776 DatabaseTask::CancelCompletion();
777 group_ = NULL;
778 cache_ = NULL;
781 // FindMainResponseTask -------
783 // Helpers for FindMainResponseTask::Run()
784 namespace {
785 class SortByCachePreference
786 : public std::binary_function<
787 AppCacheDatabase::EntryRecord,
788 AppCacheDatabase::EntryRecord,
789 bool> {
790 public:
791 SortByCachePreference(int64 preferred_id, const std::set<int64>& in_use_ids)
792 : preferred_id_(preferred_id), in_use_ids_(in_use_ids) {
794 bool operator()(
795 const AppCacheDatabase::EntryRecord& lhs,
796 const AppCacheDatabase::EntryRecord& rhs) {
797 return compute_value(lhs) > compute_value(rhs);
799 private:
800 int compute_value(const AppCacheDatabase::EntryRecord& entry) {
801 if (entry.cache_id == preferred_id_)
802 return 100;
803 else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end())
804 return 50;
805 return 0;
807 int64 preferred_id_;
808 const std::set<int64>& in_use_ids_;
811 bool SortByLength(
812 const AppCacheDatabase::NamespaceRecord& lhs,
813 const AppCacheDatabase::NamespaceRecord& rhs) {
814 return lhs.namespace_.namespace_url.spec().length() >
815 rhs.namespace_.namespace_url.spec().length();
818 class NetworkNamespaceHelper {
819 public:
820 explicit NetworkNamespaceHelper(AppCacheDatabase* database)
821 : database_(database) {
824 bool IsInNetworkNamespace(const GURL& url, int64 cache_id) {
825 typedef std::pair<WhiteListMap::iterator, bool> InsertResult;
826 InsertResult result = namespaces_map_.insert(
827 WhiteListMap::value_type(cache_id, NamespaceVector()));
828 if (result.second)
829 GetOnlineWhiteListForCache(cache_id, &result.first->second);
830 return AppCache::FindNamespace(result.first->second, url) != NULL;
833 private:
834 void GetOnlineWhiteListForCache(
835 int64 cache_id, NamespaceVector* namespaces) {
836 DCHECK(namespaces && namespaces->empty());
837 typedef std::vector<AppCacheDatabase::OnlineWhiteListRecord>
838 WhiteListVector;
839 WhiteListVector records;
840 if (!database_->FindOnlineWhiteListForCache(cache_id, &records))
841 return;
842 WhiteListVector::const_iterator iter = records.begin();
843 while (iter != records.end()) {
844 namespaces->push_back(
845 Namespace(NETWORK_NAMESPACE, iter->namespace_url, GURL(),
846 iter->is_pattern));
847 ++iter;
851 // Key is cache id
852 typedef std::map<int64, NamespaceVector> WhiteListMap;
853 WhiteListMap namespaces_map_;
854 AppCacheDatabase* database_;
857 } // namespace
859 class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask {
860 public:
861 FindMainResponseTask(AppCacheStorageImpl* storage,
862 const GURL& url,
863 const GURL& preferred_manifest_url,
864 const AppCacheWorkingSet::GroupMap* groups_in_use)
865 : DatabaseTask(storage), url_(url),
866 preferred_manifest_url_(preferred_manifest_url),
867 cache_id_(kNoCacheId), group_id_(0) {
868 if (groups_in_use) {
869 for (AppCacheWorkingSet::GroupMap::const_iterator it =
870 groups_in_use->begin();
871 it != groups_in_use->end(); ++it) {
872 AppCacheGroup* group = it->second;
873 AppCache* cache = group->newest_complete_cache();
874 if (group->is_obsolete() || !cache)
875 continue;
876 cache_ids_in_use_.insert(cache->cache_id());
881 // DatabaseTask:
882 virtual void Run() OVERRIDE;
883 virtual void RunCompleted() OVERRIDE;
885 protected:
886 virtual ~FindMainResponseTask() {}
888 private:
889 typedef std::vector<AppCacheDatabase::NamespaceRecord*>
890 NamespaceRecordPtrVector;
892 bool FindExactMatch(int64 preferred_id);
893 bool FindNamespaceMatch(int64 preferred_id);
894 bool FindNamespaceHelper(
895 int64 preferred_cache_id,
896 AppCacheDatabase::NamespaceRecordVector* namespaces,
897 NetworkNamespaceHelper* network_namespace_helper);
898 bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces);
900 GURL url_;
901 GURL preferred_manifest_url_;
902 std::set<int64> cache_ids_in_use_;
903 AppCacheEntry entry_;
904 AppCacheEntry fallback_entry_;
905 GURL namespace_entry_url_;
906 int64 cache_id_;
907 int64 group_id_;
908 GURL manifest_url_;
913 void AppCacheStorageImpl::FindMainResponseTask::Run() {
914 // NOTE: The heuristics around choosing amoungst multiple candidates
915 // is underspecified, and just plain not fully understood. This needs
916 // to be refined.
918 // The 'preferred_manifest_url' is the url of the manifest associated
919 // with the page that opened or embedded the page being loaded now.
920 // We have a strong preference to use resources from that cache.
921 // We also have a lesser bias to use resources from caches that are currently
922 // being used by other unrelated pages.
923 // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases
924 // - when navigating a frame whose current contents are from an appcache
925 // - when clicking an href in a frame that is appcached
926 int64 preferred_cache_id = kNoCacheId;
927 if (!preferred_manifest_url_.is_empty()) {
928 AppCacheDatabase::GroupRecord preferred_group;
929 AppCacheDatabase::CacheRecord preferred_cache;
930 if (database_->FindGroupForManifestUrl(
931 preferred_manifest_url_, &preferred_group) &&
932 database_->FindCacheForGroup(
933 preferred_group.group_id, &preferred_cache)) {
934 preferred_cache_id = preferred_cache.cache_id;
938 if (FindExactMatch(preferred_cache_id) ||
939 FindNamespaceMatch(preferred_cache_id)) {
940 // We found something.
941 DCHECK(cache_id_ != kNoCacheId && !manifest_url_.is_empty() &&
942 group_id_ != 0);
943 return;
946 // We didn't find anything.
947 DCHECK(cache_id_ == kNoCacheId && manifest_url_.is_empty() &&
948 group_id_ == 0);
951 bool AppCacheStorageImpl::
952 FindMainResponseTask::FindExactMatch(int64 preferred_cache_id) {
953 std::vector<AppCacheDatabase::EntryRecord> entries;
954 if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) {
955 // Sort them in order of preference, from the preferred_cache first,
956 // followed by hits from caches that are 'in use', then the rest.
957 std::sort(entries.begin(), entries.end(),
958 SortByCachePreference(preferred_cache_id, cache_ids_in_use_));
960 // Take the first with a valid, non-foreign entry.
961 std::vector<AppCacheDatabase::EntryRecord>::iterator iter;
962 for (iter = entries.begin(); iter < entries.end(); ++iter) {
963 AppCacheDatabase::GroupRecord group_record;
964 if ((iter->flags & AppCacheEntry::FOREIGN) ||
965 !database_->FindGroupForCache(iter->cache_id, &group_record)) {
966 continue;
968 manifest_url_ = group_record.manifest_url;
969 group_id_ = group_record.group_id;
970 entry_ = AppCacheEntry(iter->flags, iter->response_id);
971 cache_id_ = iter->cache_id;
972 return true; // We found an exact match.
975 return false;
978 bool AppCacheStorageImpl::
979 FindMainResponseTask::FindNamespaceMatch(int64 preferred_cache_id) {
980 AppCacheDatabase::NamespaceRecordVector all_intercepts;
981 AppCacheDatabase::NamespaceRecordVector all_fallbacks;
982 if (!database_->FindNamespacesForOrigin(
983 url_.GetOrigin(), &all_intercepts, &all_fallbacks)
984 || (all_intercepts.empty() && all_fallbacks.empty())) {
985 return false;
988 NetworkNamespaceHelper network_namespace_helper(database_);
989 if (FindNamespaceHelper(preferred_cache_id,
990 &all_intercepts,
991 &network_namespace_helper) ||
992 FindNamespaceHelper(preferred_cache_id,
993 &all_fallbacks,
994 &network_namespace_helper)) {
995 return true;
997 return false;
1000 bool AppCacheStorageImpl::
1001 FindMainResponseTask::FindNamespaceHelper(
1002 int64 preferred_cache_id,
1003 AppCacheDatabase::NamespaceRecordVector* namespaces,
1004 NetworkNamespaceHelper* network_namespace_helper) {
1005 // Sort them by length, longer matches within the same cache/bucket take
1006 // precedence.
1007 std::sort(namespaces->begin(), namespaces->end(), SortByLength);
1009 NamespaceRecordPtrVector preferred_namespaces;
1010 NamespaceRecordPtrVector inuse_namespaces;
1011 NamespaceRecordPtrVector other_namespaces;
1012 std::vector<AppCacheDatabase::NamespaceRecord>::iterator iter;
1013 for (iter = namespaces->begin(); iter < namespaces->end(); ++iter) {
1014 // Skip those that aren't a match.
1015 if (!iter->namespace_.IsMatch(url_))
1016 continue;
1018 // Skip namespaces where the requested url falls into a network
1019 // namespace of its containing appcache.
1020 if (network_namespace_helper->IsInNetworkNamespace(url_, iter->cache_id))
1021 continue;
1023 // Bin them into one of our three buckets.
1024 if (iter->cache_id == preferred_cache_id)
1025 preferred_namespaces.push_back(&(*iter));
1026 else if (cache_ids_in_use_.find(iter->cache_id) != cache_ids_in_use_.end())
1027 inuse_namespaces.push_back(&(*iter));
1028 else
1029 other_namespaces.push_back(&(*iter));
1032 if (FindFirstValidNamespace(preferred_namespaces) ||
1033 FindFirstValidNamespace(inuse_namespaces) ||
1034 FindFirstValidNamespace(other_namespaces))
1035 return true; // We found one.
1037 // We didn't find anything.
1038 return false;
1041 bool AppCacheStorageImpl::
1042 FindMainResponseTask::FindFirstValidNamespace(
1043 const NamespaceRecordPtrVector& namespaces) {
1044 // Take the first with a valid, non-foreign entry.
1045 NamespaceRecordPtrVector::const_iterator iter;
1046 for (iter = namespaces.begin(); iter < namespaces.end(); ++iter) {
1047 AppCacheDatabase::EntryRecord entry_record;
1048 if (database_->FindEntry((*iter)->cache_id, (*iter)->namespace_.target_url,
1049 &entry_record)) {
1050 AppCacheDatabase::GroupRecord group_record;
1051 if ((entry_record.flags & AppCacheEntry::FOREIGN) ||
1052 !database_->FindGroupForCache(entry_record.cache_id, &group_record)) {
1053 continue;
1055 manifest_url_ = group_record.manifest_url;
1056 group_id_ = group_record.group_id;
1057 cache_id_ = (*iter)->cache_id;
1058 namespace_entry_url_ = (*iter)->namespace_.target_url;
1059 if ((*iter)->namespace_.type == FALLBACK_NAMESPACE)
1060 fallback_entry_ = AppCacheEntry(entry_record.flags,
1061 entry_record.response_id);
1062 else
1063 entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id);
1064 return true; // We found one.
1067 return false; // We didn't find a match.
1070 void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() {
1071 storage_->CallOnMainResponseFound(
1072 &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_,
1073 cache_id_, group_id_, manifest_url_);
1076 // MarkEntryAsForeignTask -------
1078 class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask {
1079 public:
1080 MarkEntryAsForeignTask(
1081 AppCacheStorageImpl* storage, const GURL& url, int64 cache_id)
1082 : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {}
1084 // DatabaseTask:
1085 virtual void Run() OVERRIDE;
1086 virtual void RunCompleted() OVERRIDE;
1088 protected:
1089 virtual ~MarkEntryAsForeignTask() {}
1091 private:
1092 int64 cache_id_;
1093 GURL entry_url_;
1096 void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() {
1097 database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN);
1100 void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() {
1101 DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ &&
1102 storage_->pending_foreign_markings_.front().second == cache_id_);
1103 storage_->pending_foreign_markings_.pop_front();
1106 // MakeGroupObsoleteTask -------
1108 class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask {
1109 public:
1110 MakeGroupObsoleteTask(AppCacheStorageImpl* storage, AppCacheGroup* group);
1112 // DatabaseTask:
1113 virtual void Run() OVERRIDE;
1114 virtual void RunCompleted() OVERRIDE;
1115 virtual void CancelCompletion() OVERRIDE;
1117 protected:
1118 virtual ~MakeGroupObsoleteTask() {}
1120 private:
1121 scoped_refptr<AppCacheGroup> group_;
1122 int64 group_id_;
1123 GURL origin_;
1124 bool success_;
1125 int64 new_origin_usage_;
1126 std::vector<int64> newly_deletable_response_ids_;
1129 AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask(
1130 AppCacheStorageImpl* storage, AppCacheGroup* group)
1131 : DatabaseTask(storage), group_(group), group_id_(group->group_id()),
1132 origin_(group->manifest_url().GetOrigin()),
1133 success_(false), new_origin_usage_(-1) {
1136 void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() {
1137 DCHECK(!success_);
1138 sql::Connection* connection = database_->db_connection();
1139 if (!connection)
1140 return;
1142 sql::Transaction transaction(connection);
1143 if (!transaction.Begin())
1144 return;
1146 AppCacheDatabase::GroupRecord group_record;
1147 if (!database_->FindGroup(group_id_, &group_record)) {
1148 // This group doesn't exists in the database, nothing todo here.
1149 new_origin_usage_ = database_->GetOriginUsage(origin_);
1150 success_ = true;
1151 return;
1154 DCHECK_EQ(group_record.origin, origin_);
1155 success_ = DeleteGroupAndRelatedRecords(database_,
1156 group_id_,
1157 &newly_deletable_response_ids_);
1159 new_origin_usage_ = database_->GetOriginUsage(origin_);
1160 success_ = success_ && transaction.Commit();
1163 void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() {
1164 if (success_) {
1165 group_->set_obsolete(true);
1166 if (!storage_->is_disabled()) {
1167 storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_);
1168 group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_);
1170 // Also remove from the working set, caches for an 'obsolete' group
1171 // may linger in use, but the group itself cannot be looked up by
1172 // 'manifest_url' in the working set any longer.
1173 storage_->working_set()->RemoveGroup(group_);
1176 FOR_EACH_DELEGATE(delegates_, OnGroupMadeObsolete(group_, success_));
1177 group_ = NULL;
1180 void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() {
1181 // Overriden to safely drop our reference to the group
1182 // which is not thread safe refcounted.
1183 DatabaseTask::CancelCompletion();
1184 group_ = NULL;
1187 // GetDeletableResponseIdsTask -------
1189 class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask {
1190 public:
1191 GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64 max_rowid)
1192 : DatabaseTask(storage), max_rowid_(max_rowid) {}
1194 // DatabaseTask:
1195 virtual void Run() OVERRIDE;
1196 virtual void RunCompleted() OVERRIDE;
1198 protected:
1199 virtual ~GetDeletableResponseIdsTask() {}
1201 private:
1202 int64 max_rowid_;
1203 std::vector<int64> response_ids_;
1206 void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() {
1207 const int kSqlLimit = 1000;
1208 database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit);
1209 // TODO(michaeln): retrieve group_ids too
1212 void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() {
1213 if (!response_ids_.empty())
1214 storage_->StartDeletingResponses(response_ids_);
1217 // InsertDeletableResponseIdsTask -------
1219 class AppCacheStorageImpl::InsertDeletableResponseIdsTask
1220 : public DatabaseTask {
1221 public:
1222 explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1223 : DatabaseTask(storage) {}
1225 // DatabaseTask:
1226 virtual void Run() OVERRIDE;
1228 std::vector<int64> response_ids_;
1230 protected:
1231 virtual ~InsertDeletableResponseIdsTask() {}
1234 void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() {
1235 database_->InsertDeletableResponseIds(response_ids_);
1236 // TODO(michaeln): store group_ids too
1239 // DeleteDeletableResponseIdsTask -------
1241 class AppCacheStorageImpl::DeleteDeletableResponseIdsTask
1242 : public DatabaseTask {
1243 public:
1244 explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage)
1245 : DatabaseTask(storage) {}
1247 // DatabaseTask:
1248 virtual void Run() OVERRIDE;
1250 std::vector<int64> response_ids_;
1252 protected:
1253 virtual ~DeleteDeletableResponseIdsTask() {}
1256 void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() {
1257 database_->DeleteDeletableResponseIds(response_ids_);
1260 // UpdateGroupLastAccessTimeTask -------
1262 class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask
1263 : public DatabaseTask {
1264 public:
1265 UpdateGroupLastAccessTimeTask(
1266 AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time)
1267 : DatabaseTask(storage), group_id_(group->group_id()),
1268 last_access_time_(time) {
1269 storage->NotifyStorageAccessed(group->manifest_url().GetOrigin());
1272 // DatabaseTask:
1273 virtual void Run() OVERRIDE;
1275 protected:
1276 virtual ~UpdateGroupLastAccessTimeTask() {}
1278 private:
1279 int64 group_id_;
1280 base::Time last_access_time_;
1283 void AppCacheStorageImpl::UpdateGroupLastAccessTimeTask::Run() {
1284 database_->UpdateGroupLastAccessTime(group_id_, last_access_time_);
1288 // AppCacheStorageImpl ---------------------------------------------------
1290 AppCacheStorageImpl::AppCacheStorageImpl(AppCacheService* service)
1291 : AppCacheStorage(service),
1292 is_incognito_(false),
1293 is_response_deletion_scheduled_(false),
1294 did_start_deleting_responses_(false),
1295 last_deletable_response_rowid_(0),
1296 database_(NULL),
1297 is_disabled_(false),
1298 weak_factory_(this) {
1301 AppCacheStorageImpl::~AppCacheStorageImpl() {
1302 std::for_each(pending_quota_queries_.begin(),
1303 pending_quota_queries_.end(),
1304 std::mem_fun(&DatabaseTask::CancelCompletion));
1305 std::for_each(scheduled_database_tasks_.begin(),
1306 scheduled_database_tasks_.end(),
1307 std::mem_fun(&DatabaseTask::CancelCompletion));
1309 if (database_ &&
1310 !db_thread_->PostTask(
1311 FROM_HERE,
1312 base::Bind(&ClearSessionOnlyOrigins, database_,
1313 make_scoped_refptr(service_->special_storage_policy()),
1314 service()->force_keep_session_state()))) {
1315 delete database_;
1319 void AppCacheStorageImpl::Initialize(const base::FilePath& cache_directory,
1320 base::MessageLoopProxy* db_thread,
1321 base::MessageLoopProxy* cache_thread) {
1322 DCHECK(db_thread);
1324 cache_directory_ = cache_directory;
1325 is_incognito_ = cache_directory_.empty();
1327 base::FilePath db_file_path;
1328 if (!is_incognito_)
1329 db_file_path = cache_directory_.Append(kAppCacheDatabaseName);
1330 database_ = new AppCacheDatabase(db_file_path);
1332 db_thread_ = db_thread;
1333 cache_thread_ = cache_thread;
1335 scoped_refptr<InitTask> task(new InitTask(this));
1336 task->Schedule();
1339 void AppCacheStorageImpl::Disable() {
1340 if (is_disabled_)
1341 return;
1342 VLOG(1) << "Disabling appcache storage.";
1343 is_disabled_ = true;
1344 ClearUsageMapAndNotify();
1345 working_set()->Disable();
1346 if (disk_cache_)
1347 disk_cache_->Disable();
1348 scoped_refptr<DisableDatabaseTask> task(new DisableDatabaseTask(this));
1349 task->Schedule();
1352 void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) {
1353 DCHECK(delegate);
1354 scoped_refptr<GetAllInfoTask> task(new GetAllInfoTask(this));
1355 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1356 task->Schedule();
1359 void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) {
1360 DCHECK(delegate);
1361 if (is_disabled_) {
1362 delegate->OnCacheLoaded(NULL, id);
1363 return;
1366 AppCache* cache = working_set_.GetCache(id);
1367 if (cache) {
1368 delegate->OnCacheLoaded(cache, id);
1369 if (cache->owning_group()) {
1370 scoped_refptr<DatabaseTask> update_task(
1371 new UpdateGroupLastAccessTimeTask(
1372 this, cache->owning_group(), base::Time::Now()));
1373 update_task->Schedule();
1375 return;
1377 scoped_refptr<CacheLoadTask> task(GetPendingCacheLoadTask(id));
1378 if (task) {
1379 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1380 return;
1382 task = new CacheLoadTask(id, this);
1383 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1384 task->Schedule();
1385 pending_cache_loads_[id] = task;
1388 void AppCacheStorageImpl::LoadOrCreateGroup(
1389 const GURL& manifest_url, Delegate* delegate) {
1390 DCHECK(delegate);
1391 if (is_disabled_) {
1392 delegate->OnGroupLoaded(NULL, manifest_url);
1393 return;
1396 AppCacheGroup* group = working_set_.GetGroup(manifest_url);
1397 if (group) {
1398 delegate->OnGroupLoaded(group, manifest_url);
1399 scoped_refptr<DatabaseTask> update_task(
1400 new UpdateGroupLastAccessTimeTask(
1401 this, group, base::Time::Now()));
1402 update_task->Schedule();
1403 return;
1406 scoped_refptr<GroupLoadTask> task(GetPendingGroupLoadTask(manifest_url));
1407 if (task) {
1408 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1409 return;
1412 if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) {
1413 // No need to query the database, return a new group immediately.
1414 scoped_refptr<AppCacheGroup> group(new AppCacheGroup(
1415 service_->storage(), manifest_url, NewGroupId()));
1416 delegate->OnGroupLoaded(group, manifest_url);
1417 return;
1420 task = new GroupLoadTask(manifest_url, this);
1421 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1422 task->Schedule();
1423 pending_group_loads_[manifest_url] = task.get();
1426 void AppCacheStorageImpl::StoreGroupAndNewestCache(
1427 AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) {
1428 // TODO(michaeln): distinguish between a simple update of an existing
1429 // cache that just adds new master entry(s), and the insertion of a
1430 // whole new cache. The StoreGroupAndCacheTask as written will handle
1431 // the simple update case in a very heavy weight way (delete all and
1432 // the reinsert all over again).
1433 DCHECK(group && delegate && newest_cache);
1434 scoped_refptr<StoreGroupAndCacheTask> task(
1435 new StoreGroupAndCacheTask(this, group, newest_cache));
1436 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1437 task->GetQuotaThenSchedule();
1439 // TODO(michaeln): histogram is fishing for clues to crbug/95101
1440 if (!newest_cache->GetEntry(group->manifest_url())) {
1441 AppCacheHistograms::AddMissingManifestDetectedAtCallsite(
1442 AppCacheHistograms::CALLSITE_3);
1446 void AppCacheStorageImpl::FindResponseForMainRequest(
1447 const GURL& url, const GURL& preferred_manifest_url,
1448 Delegate* delegate) {
1449 DCHECK(delegate);
1451 const GURL* url_ptr = &url;
1452 GURL url_no_ref;
1453 if (url.has_ref()) {
1454 GURL::Replacements replacements;
1455 replacements.ClearRef();
1456 url_no_ref = url.ReplaceComponents(replacements);
1457 url_ptr = &url_no_ref;
1460 const GURL origin = url.GetOrigin();
1462 // First look in our working set for a direct hit without having to query
1463 // the database.
1464 const AppCacheWorkingSet::GroupMap* groups_in_use =
1465 working_set()->GetGroupsInOrigin(origin);
1466 if (groups_in_use) {
1467 if (!preferred_manifest_url.is_empty()) {
1468 AppCacheWorkingSet::GroupMap::const_iterator found =
1469 groups_in_use->find(preferred_manifest_url);
1470 if (found != groups_in_use->end() &&
1471 FindResponseForMainRequestInGroup(
1472 found->second, *url_ptr, delegate)) {
1473 return;
1475 } else {
1476 for (AppCacheWorkingSet::GroupMap::const_iterator it =
1477 groups_in_use->begin();
1478 it != groups_in_use->end(); ++it) {
1479 if (FindResponseForMainRequestInGroup(
1480 it->second, *url_ptr, delegate)) {
1481 return;
1487 if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) {
1488 // No need to query the database, return async'ly but without going thru
1489 // the DB thread.
1490 scoped_refptr<AppCacheGroup> no_group;
1491 scoped_refptr<AppCache> no_cache;
1492 ScheduleSimpleTask(
1493 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1494 weak_factory_.GetWeakPtr(), url, AppCacheEntry(), no_group,
1495 no_cache,
1496 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
1497 return;
1500 // We have to query the database, schedule a database task to do so.
1501 scoped_refptr<FindMainResponseTask> task(
1502 new FindMainResponseTask(this, *url_ptr, preferred_manifest_url,
1503 groups_in_use));
1504 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1505 task->Schedule();
1508 bool AppCacheStorageImpl::FindResponseForMainRequestInGroup(
1509 AppCacheGroup* group, const GURL& url, Delegate* delegate) {
1510 AppCache* cache = group->newest_complete_cache();
1511 if (group->is_obsolete() || !cache)
1512 return false;
1514 AppCacheEntry* entry = cache->GetEntry(url);
1515 if (!entry || entry->IsForeign())
1516 return false;
1518 ScheduleSimpleTask(
1519 base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse,
1520 weak_factory_.GetWeakPtr(), url, *entry,
1521 make_scoped_refptr(group), make_scoped_refptr(cache),
1522 make_scoped_refptr(GetOrCreateDelegateReference(delegate))));
1523 return true;
1526 void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse(
1527 const GURL& url,
1528 const AppCacheEntry& found_entry,
1529 scoped_refptr<AppCacheGroup> group,
1530 scoped_refptr<AppCache> cache,
1531 scoped_refptr<DelegateReference> delegate_ref) {
1532 if (delegate_ref->delegate) {
1533 DelegateReferenceVector delegates(1, delegate_ref);
1534 CallOnMainResponseFound(
1535 &delegates, url, found_entry,
1536 GURL(), AppCacheEntry(),
1537 cache.get() ? cache->cache_id() : kNoCacheId,
1538 group.get() ? group->group_id() : kNoCacheId,
1539 group.get() ? group->manifest_url() : GURL());
1543 void AppCacheStorageImpl::CallOnMainResponseFound(
1544 DelegateReferenceVector* delegates,
1545 const GURL& url, const AppCacheEntry& entry,
1546 const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry,
1547 int64 cache_id, int64 group_id, const GURL& manifest_url) {
1548 FOR_EACH_DELEGATE(
1549 (*delegates),
1550 OnMainResponseFound(url, entry,
1551 namespace_entry_url, fallback_entry,
1552 cache_id, group_id, manifest_url));
1555 void AppCacheStorageImpl::FindResponseForSubRequest(
1556 AppCache* cache, const GURL& url,
1557 AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry,
1558 bool* found_network_namespace) {
1559 DCHECK(cache && cache->is_complete());
1561 // When a group is forcibly deleted, all subresource loads for pages
1562 // using caches in the group will result in a synthesized network errors.
1563 // Forcible deletion is not a function that is covered by the HTML5 spec.
1564 if (cache->owning_group()->is_being_deleted()) {
1565 *found_entry = AppCacheEntry();
1566 *found_fallback_entry = AppCacheEntry();
1567 *found_network_namespace = false;
1568 return;
1571 GURL fallback_namespace_not_used;
1572 GURL intercept_namespace_not_used;
1573 cache->FindResponseForRequest(
1574 url, found_entry, &intercept_namespace_not_used,
1575 found_fallback_entry, &fallback_namespace_not_used,
1576 found_network_namespace);
1579 void AppCacheStorageImpl::MarkEntryAsForeign(
1580 const GURL& entry_url, int64 cache_id) {
1581 AppCache* cache = working_set_.GetCache(cache_id);
1582 if (cache) {
1583 AppCacheEntry* entry = cache->GetEntry(entry_url);
1584 DCHECK(entry);
1585 if (entry)
1586 entry->add_types(AppCacheEntry::FOREIGN);
1588 scoped_refptr<MarkEntryAsForeignTask> task(
1589 new MarkEntryAsForeignTask(this, entry_url, cache_id));
1590 task->Schedule();
1591 pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id));
1594 void AppCacheStorageImpl::MakeGroupObsolete(
1595 AppCacheGroup* group, Delegate* delegate) {
1596 DCHECK(group && delegate);
1597 scoped_refptr<MakeGroupObsoleteTask> task(
1598 new MakeGroupObsoleteTask(this, group));
1599 task->AddDelegate(GetOrCreateDelegateReference(delegate));
1600 task->Schedule();
1603 AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader(
1604 const GURL& manifest_url, int64 group_id, int64 response_id) {
1605 return new AppCacheResponseReader(response_id, group_id, disk_cache());
1608 AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter(
1609 const GURL& manifest_url, int64 group_id) {
1610 return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache());
1613 void AppCacheStorageImpl::DoomResponses(
1614 const GURL& manifest_url, const std::vector<int64>& response_ids) {
1615 if (response_ids.empty())
1616 return;
1618 // Start deleting them from the disk cache lazily.
1619 StartDeletingResponses(response_ids);
1621 // Also schedule a database task to record these ids in the
1622 // deletable responses table.
1623 // TODO(michaeln): There is a race here. If the browser crashes
1624 // prior to committing these rows to the database and prior to us
1625 // having deleted them from the disk cache, we'll never delete them.
1626 scoped_refptr<InsertDeletableResponseIdsTask> task(
1627 new InsertDeletableResponseIdsTask(this));
1628 task->response_ids_ = response_ids;
1629 task->Schedule();
1632 void AppCacheStorageImpl::DeleteResponses(
1633 const GURL& manifest_url, const std::vector<int64>& response_ids) {
1634 if (response_ids.empty())
1635 return;
1636 StartDeletingResponses(response_ids);
1639 void AppCacheStorageImpl::PurgeMemory() {
1640 scoped_refptr<CloseConnectionTask> task(new CloseConnectionTask(this));
1641 task->Schedule();
1644 void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() {
1645 // Only if we haven't already begun.
1646 if (!did_start_deleting_responses_) {
1647 scoped_refptr<GetDeletableResponseIdsTask> task(
1648 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
1649 task->Schedule();
1653 void AppCacheStorageImpl::StartDeletingResponses(
1654 const std::vector<int64>& response_ids) {
1655 DCHECK(!response_ids.empty());
1656 did_start_deleting_responses_ = true;
1657 deletable_response_ids_.insert(
1658 deletable_response_ids_.end(),
1659 response_ids.begin(), response_ids.end());
1660 if (!is_response_deletion_scheduled_)
1661 ScheduleDeleteOneResponse();
1664 void AppCacheStorageImpl::ScheduleDeleteOneResponse() {
1665 DCHECK(!is_response_deletion_scheduled_);
1666 const base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(10);
1667 base::MessageLoop::current()->PostDelayedTask(
1668 FROM_HERE,
1669 base::Bind(&AppCacheStorageImpl::DeleteOneResponse,
1670 weak_factory_.GetWeakPtr()),
1671 kDelay);
1672 is_response_deletion_scheduled_ = true;
1675 void AppCacheStorageImpl::DeleteOneResponse() {
1676 DCHECK(is_response_deletion_scheduled_);
1677 DCHECK(!deletable_response_ids_.empty());
1679 if (!disk_cache()) {
1680 DCHECK(is_disabled_);
1681 deletable_response_ids_.clear();
1682 deleted_response_ids_.clear();
1683 is_response_deletion_scheduled_ = false;
1684 return;
1687 // TODO(michaeln): add group_id to DoomEntry args
1688 int64 id = deletable_response_ids_.front();
1689 int rv = disk_cache_->DoomEntry(
1690 id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse,
1691 base::Unretained(this)));
1692 if (rv != net::ERR_IO_PENDING)
1693 OnDeletedOneResponse(rv);
1696 void AppCacheStorageImpl::OnDeletedOneResponse(int rv) {
1697 is_response_deletion_scheduled_ = false;
1698 if (is_disabled_)
1699 return;
1701 int64 id = deletable_response_ids_.front();
1702 deletable_response_ids_.pop_front();
1703 if (rv != net::ERR_ABORTED)
1704 deleted_response_ids_.push_back(id);
1706 const size_t kBatchSize = 50U;
1707 if (deleted_response_ids_.size() >= kBatchSize ||
1708 deletable_response_ids_.empty()) {
1709 scoped_refptr<DeleteDeletableResponseIdsTask> task(
1710 new DeleteDeletableResponseIdsTask(this));
1711 task->response_ids_.swap(deleted_response_ids_);
1712 task->Schedule();
1715 if (deletable_response_ids_.empty()) {
1716 scoped_refptr<GetDeletableResponseIdsTask> task(
1717 new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_));
1718 task->Schedule();
1719 return;
1722 ScheduleDeleteOneResponse();
1725 AppCacheStorageImpl::CacheLoadTask*
1726 AppCacheStorageImpl::GetPendingCacheLoadTask(int64 cache_id) {
1727 PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id);
1728 if (found != pending_cache_loads_.end())
1729 return found->second;
1730 return NULL;
1733 AppCacheStorageImpl::GroupLoadTask*
1734 AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) {
1735 PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url);
1736 if (found != pending_group_loads_.end())
1737 return found->second;
1738 return NULL;
1741 void AppCacheStorageImpl::GetPendingForeignMarkingsForCache(
1742 int64 cache_id, std::vector<GURL>* urls) {
1743 PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin();
1744 while (iter != pending_foreign_markings_.end()) {
1745 if (iter->second == cache_id)
1746 urls->push_back(iter->first);
1747 ++iter;
1751 void AppCacheStorageImpl::ScheduleSimpleTask(const base::Closure& task) {
1752 pending_simple_tasks_.push_back(task);
1753 base::MessageLoop::current()->PostTask(
1754 FROM_HERE,
1755 base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask,
1756 weak_factory_.GetWeakPtr()));
1759 void AppCacheStorageImpl::RunOnePendingSimpleTask() {
1760 DCHECK(!pending_simple_tasks_.empty());
1761 base::Closure task = pending_simple_tasks_.front();
1762 pending_simple_tasks_.pop_front();
1763 task.Run();
1766 AppCacheDiskCache* AppCacheStorageImpl::disk_cache() {
1767 DCHECK(IsInitTaskComplete());
1769 if (is_disabled_)
1770 return NULL;
1772 if (!disk_cache_) {
1773 int rv = net::OK;
1774 disk_cache_.reset(new AppCacheDiskCache);
1775 if (is_incognito_) {
1776 rv = disk_cache_->InitWithMemBackend(
1777 kMaxMemDiskCacheSize,
1778 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
1779 base::Unretained(this)));
1780 } else {
1781 rv = disk_cache_->InitWithDiskBackend(
1782 cache_directory_.Append(kDiskCacheDirectoryName),
1783 kMaxDiskCacheSize, false, cache_thread_,
1784 base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized,
1785 base::Unretained(this)));
1788 // We should not keep this reference around.
1789 cache_thread_ = NULL;
1791 if (rv != net::ERR_IO_PENDING)
1792 OnDiskCacheInitialized(rv);
1794 return disk_cache_.get();
1797 void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) {
1798 if (rv != net::OK) {
1799 LOG(ERROR) << "Failed to open the appcache diskcache.";
1800 AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR);
1802 // We're unable to open the disk cache, this is a fatal error that we can't
1803 // really recover from. We handle it by disabling the appcache for this
1804 // browser session and deleting the directory on disk. The next browser
1805 // session should start with a clean slate.
1806 Disable();
1807 if (!is_incognito_) {
1808 VLOG(1) << "Deleting existing appcache data and starting over.";
1809 db_thread_->PostTask(
1810 FROM_HERE, base::Bind(base::IgnoreResult(&file_util::Delete),
1811 cache_directory_, true));
1816 } // namespace appcache