| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "webkit/appcache/appcache_storage_impl.h" |
| |
| #include "app/sql/connection.h" |
| #include "app/sql/transaction.h" |
| #include "base/file_util.h" |
| #include "base/logging.h" |
| #include "base/message_loop.h" |
| #include "base/stl_util-inl.h" |
| #include "base/string_util.h" |
| #include "net/base/cache_type.h" |
| #include "net/base/net_errors.h" |
| #include "webkit/appcache/appcache.h" |
| #include "webkit/appcache/appcache_database.h" |
| #include "webkit/appcache/appcache_entry.h" |
| #include "webkit/appcache/appcache_group.h" |
| #include "webkit/appcache/appcache_histograms.h" |
| #include "webkit/appcache/appcache_policy.h" |
| #include "webkit/appcache/appcache_response.h" |
| #include "webkit/appcache/appcache_service.h" |
| #include "webkit/appcache/appcache_thread.h" |
| |
| namespace { |
| // Helper with no return value for use with NewRunnableFunction. |
| void DeleteDirectory(const FilePath& path) { |
| file_util::Delete(path, true); |
| } |
| } |
| |
| namespace appcache { |
| |
| static const int kMaxDiskCacheSize = 250 * 1024 * 1024; |
| static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024; |
| static const FilePath::CharType kDiskCacheDirectoryName[] = |
| FILE_PATH_LITERAL("Cache"); |
| |
| // DatabaseTask ----------------------------------------- |
| |
| class AppCacheStorageImpl::DatabaseTask |
| : public base::RefCountedThreadSafe<DatabaseTask> { |
| public: |
| explicit DatabaseTask(AppCacheStorageImpl* storage) |
| : storage_(storage), database_(storage->database_) {} |
| |
| virtual ~DatabaseTask() {} |
| |
| void AddDelegate(DelegateReference* delegate_reference) { |
| delegates_.push_back(delegate_reference); |
| } |
| |
| // Schedules a task to be Run() on the DB thread. Tasks |
| // are run in the order in which they are scheduled. |
| void Schedule(); |
| |
| // Called on the DB thread. |
| virtual void Run() = 0; |
| |
| // Called on the IO thread after Run() has completed. |
| virtual void RunCompleted() {} |
| |
| // Once scheduled a task cannot be cancelled, but the |
| // call to RunCompleted may be. This method should only be |
| // called on the IO thread. This is used by AppCacheStorageImpl |
| // to cancel the completion calls when AppCacheStorageImpl is |
| // destructed. This method may be overriden to release or delete |
| // additional data associated with the task that is not DB thread |
| // safe. If overriden, this base class method must be called from |
| // within the override. |
| virtual void CancelCompletion(); |
| |
| protected: |
| AppCacheStorageImpl* storage_; |
| AppCacheDatabase* database_; |
| DelegateReferenceVector delegates_; |
| |
| private: |
| void CallRun(); |
| void CallRunCompleted(); |
| void CallDisableStorage(); |
| }; |
| |
| void AppCacheStorageImpl::DatabaseTask::Schedule() { |
| DCHECK(storage_); |
| DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io())); |
| if (AppCacheThread::PostTask(AppCacheThread::db(), FROM_HERE, |
| NewRunnableMethod(this, &DatabaseTask::CallRun))) { |
| storage_->scheduled_database_tasks_.push_back(this); |
| } else { |
| NOTREACHED() << "The database thread is not running."; |
| } |
| } |
| |
| void AppCacheStorageImpl::DatabaseTask::CancelCompletion() { |
| DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io())); |
| delegates_.clear(); |
| storage_ = NULL; |
| } |
| |
| void AppCacheStorageImpl::DatabaseTask::CallRun() { |
| DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::db())); |
| if (!database_->is_disabled()) { |
| Run(); |
| if (database_->is_disabled()) { |
| AppCacheThread::PostTask(AppCacheThread::io(), FROM_HERE, |
| NewRunnableMethod(this, &DatabaseTask::CallDisableStorage)); |
| } |
| } |
| AppCacheThread::PostTask(AppCacheThread::io(), FROM_HERE, |
| NewRunnableMethod(this, &DatabaseTask::CallRunCompleted)); |
| } |
| |
| void AppCacheStorageImpl::DatabaseTask::CallRunCompleted() { |
| if (storage_) { |
| DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io())); |
| DCHECK(storage_->scheduled_database_tasks_.front() == this); |
| storage_->scheduled_database_tasks_.pop_front(); |
| RunCompleted(); |
| delegates_.clear(); |
| } |
| } |
| |
| void AppCacheStorageImpl::DatabaseTask::CallDisableStorage() { |
| if (storage_) { |
| DCHECK(AppCacheThread::CurrentlyOn(AppCacheThread::io())); |
| storage_->Disable(); |
| } |
| } |
| |
| // InitTask ------- |
| |
| class AppCacheStorageImpl::InitTask : public DatabaseTask { |
| public: |
| explicit InitTask(AppCacheStorageImpl* storage) |
| : DatabaseTask(storage), last_group_id_(0), |
| last_cache_id_(0), last_response_id_(0), |
| last_deletable_response_rowid_(0) {} |
| |
| virtual void Run(); |
| virtual void RunCompleted(); |
| |
| int64 last_group_id_; |
| int64 last_cache_id_; |
| int64 last_response_id_; |
| int64 last_deletable_response_rowid_; |
| std::set<GURL> origins_with_groups_; |
| }; |
| |
| void AppCacheStorageImpl::InitTask::Run() { |
| database_->FindLastStorageIds( |
| &last_group_id_, &last_cache_id_, &last_response_id_, |
| &last_deletable_response_rowid_); |
| database_->FindOriginsWithGroups(&origins_with_groups_); |
| } |
| |
| void AppCacheStorageImpl::InitTask::RunCompleted() { |
| storage_->last_group_id_ = last_group_id_; |
| storage_->last_cache_id_ = last_cache_id_; |
| storage_->last_response_id_ = last_response_id_; |
| storage_->last_deletable_response_rowid_ = last_deletable_response_rowid_; |
| |
| if (!storage_->is_disabled()) { |
| storage_->origins_with_groups_.swap(origins_with_groups_); |
| |
| const int kDelayMillis = 5 * 60 * 1000; // Five minutes. |
| MessageLoop::current()->PostDelayedTask(FROM_HERE, |
| storage_->method_factory_.NewRunnableMethod( |
| &AppCacheStorageImpl::DelayedStartDeletingUnusedResponses), |
| kDelayMillis); |
| } |
| } |
| |
| // CloseConnectionTask ------- |
| |
| class AppCacheStorageImpl::CloseConnectionTask : public DatabaseTask { |
| public: |
| explicit CloseConnectionTask(AppCacheStorageImpl* storage) |
| : DatabaseTask(storage) {} |
| |
| virtual void Run() { database_->CloseConnection(); } |
| }; |
| |
| // DisableDatabaseTask ------- |
| |
| class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask { |
| public: |
| explicit DisableDatabaseTask(AppCacheStorageImpl* storage) |
| : DatabaseTask(storage) {} |
| |
| virtual void Run() { database_->Disable(); } |
| }; |
| |
| // GetAllInfoTask ------- |
| |
| class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask { |
| public: |
| explicit GetAllInfoTask(AppCacheStorageImpl* storage) |
| : DatabaseTask(storage), |
| info_collection_(new AppCacheInfoCollection()) { |
| } |
| |
| virtual void Run(); |
| virtual void RunCompleted(); |
| |
| scoped_refptr<AppCacheInfoCollection> info_collection_; |
| }; |
| |
| void AppCacheStorageImpl::GetAllInfoTask::Run() { |
| std::set<GURL> origins; |
| database_->FindOriginsWithGroups(&origins); |
| for (std::set<GURL>::const_iterator origin = origins.begin(); |
| origin != origins.end(); ++origin) { |
| AppCacheInfoVector& infos = |
| info_collection_->infos_by_origin[*origin]; |
| std::vector<AppCacheDatabase::GroupRecord> groups; |
| database_->FindGroupsForOrigin(*origin, &groups); |
| for (std::vector<AppCacheDatabase::GroupRecord>::const_iterator |
| group = groups.begin(); |
| group != groups.end(); ++group) { |
| AppCacheDatabase::CacheRecord cache_record; |
| database_->FindCacheForGroup(group->group_id, &cache_record); |
| AppCacheInfo info; |
| info.manifest_url = group->manifest_url; |
| info.creation_time = group->creation_time; |
| info.size = cache_record.cache_size; |
| info.last_access_time = group->last_access_time; |
| info.last_update_time = cache_record.update_time; |
| info.cache_id = cache_record.cache_id; |
| info.is_complete = true; |
| infos.push_back(info); |
| } |
| } |
| } |
| |
| void AppCacheStorageImpl::GetAllInfoTask::RunCompleted() { |
| DCHECK(delegates_.size() == 1); |
| FOR_EACH_DELEGATE(delegates_, OnAllInfo(info_collection_)); |
| } |
| |
| // StoreOrLoadTask ------- |
| |
| class AppCacheStorageImpl::StoreOrLoadTask : public DatabaseTask { |
| protected: |
| explicit StoreOrLoadTask(AppCacheStorageImpl* storage) |
| : DatabaseTask(storage) {} |
| |
| bool FindRelatedCacheRecords(int64 cache_id); |
| void CreateCacheAndGroupFromRecords( |
| scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group); |
| |
| AppCacheDatabase::GroupRecord group_record_; |
| AppCacheDatabase::CacheRecord cache_record_; |
| std::vector<AppCacheDatabase::EntryRecord> entry_records_; |
| std::vector<AppCacheDatabase::FallbackNameSpaceRecord> |
| fallback_namespace_records_; |
| std::vector<AppCacheDatabase::OnlineWhiteListRecord> |
| online_whitelist_records_; |
| }; |
| |
| bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords( |
| int64 cache_id) { |
| return database_->FindEntriesForCache(cache_id, &entry_records_) && |
| database_->FindFallbackNameSpacesForCache( |
| cache_id, &fallback_namespace_records_) && |
| database_->FindOnlineWhiteListForCache( |
| cache_id, &online_whitelist_records_); |
| } |
| |
| void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords( |
| scoped_refptr<AppCache>* cache, scoped_refptr<AppCacheGroup>* group) { |
| DCHECK(storage_ && cache && group); |
| |
| (*cache) = new AppCache(storage_->service_, cache_record_.cache_id); |
| cache->get()->InitializeWithDatabaseRecords( |
| cache_record_, entry_records_, fallback_namespace_records_, |
| online_whitelist_records_); |
| cache->get()->set_complete(true); |
| |
| (*group) = storage_->working_set_.GetGroup(group_record_.manifest_url); |
| if (group->get()) { |
| DCHECK(group_record_.group_id == group->get()->group_id()); |
| group->get()->AddCache(cache->get()); |
| } else { |
| (*group) = new AppCacheGroup( |
| storage_->service_, group_record_.manifest_url, |
| group_record_.group_id); |
| group->get()->set_creation_time(group_record_.creation_time); |
| group->get()->AddCache(cache->get()); |
| } |
| DCHECK(group->get()->newest_complete_cache() == cache->get()); |
| |
| // We have to update foriegn entries if MarkEntryAsForeignTasks |
| // are in flight. |
| std::vector<GURL> urls; |
| storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls); |
| for (std::vector<GURL>::iterator iter = urls.begin(); |
| iter != urls.end(); ++iter) { |
| DCHECK(cache->get()->GetEntry(*iter)); |
| cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN); |
| } |
| |
| // TODO(michaeln): Maybe verify that the responses we expect to exist |
| // do actually exist in the disk_cache (and if not then what?) |
| } |
| |
| // CacheLoadTask ------- |
| |
| class AppCacheStorageImpl::CacheLoadTask : public StoreOrLoadTask { |
| public: |
| CacheLoadTask(int64 cache_id, AppCacheStorageImpl* storage) |
| : StoreOrLoadTask(storage), cache_id_(cache_id), |
| success_(false) {} |
| |
| virtual void Run(); |
| virtual void RunCompleted(); |
| |
| int64 cache_id_; |
| bool success_; |
| }; |
| |
| void AppCacheStorageImpl::CacheLoadTask::Run() { |
| success_ = |
| database_->FindCache(cache_id_, &cache_record_) && |
| database_->FindGroup(cache_record_.group_id, &group_record_) && |
| FindRelatedCacheRecords(cache_id_); |
| |
| if (success_) |
| database_->UpdateGroupLastAccessTime(group_record_.group_id, |
| base::Time::Now()); |
| } |
| |
| void AppCacheStorageImpl::CacheLoadTask::RunCompleted() { |
| storage_->pending_cache_loads_.erase(cache_id_); |
| scoped_refptr<AppCache> cache; |
| scoped_refptr<AppCacheGroup> group; |
| if (success_ && !storage_->is_disabled()) { |
| DCHECK(cache_record_.cache_id == cache_id_); |
| DCHECK(!storage_->working_set_.GetCache(cache_record_.cache_id)); |
| CreateCacheAndGroupFromRecords(&cache, &group); |
| } |
| FOR_EACH_DELEGATE(delegates_, OnCacheLoaded(cache, cache_id_)); |
| } |
| |
| // GroupLoadTask ------- |
| |
| class AppCacheStorageImpl::GroupLoadTask : public StoreOrLoadTask { |
| public: |
| GroupLoadTask(GURL manifest_url, AppCacheStorageImpl* storage) |
| : StoreOrLoadTask(storage), manifest_url_(manifest_url), |
| success_(false) {} |
| |
| virtual void Run(); |
| virtual void RunCompleted(); |
| |
| GURL manifest_url_; |
| bool success_; |
| }; |
| |
| void AppCacheStorageImpl::GroupLoadTask::Run() { |
| success_ = |
| database_->FindGroupForManifestUrl(manifest_url_, &group_record_) && |
| database_->FindCacheForGroup(group_record_.group_id, &cache_record_) && |
| FindRelatedCacheRecords(cache_record_.cache_id); |
| |
| if (success_) |
| database_->UpdateGroupLastAccessTime(group_record_.group_id, |
| base::Time::Now()); |
| } |
| |
| void AppCacheStorageImpl::GroupLoadTask::RunCompleted() { |
| storage_->pending_group_loads_.erase(manifest_url_); |
| scoped_refptr<AppCacheGroup> group; |
| scoped_refptr<AppCache> cache; |
| if (!storage_->is_disabled()) { |
| if (success_) { |
| DCHECK(group_record_.manifest_url == manifest_url_); |
| DCHECK(!storage_->working_set_.GetGroup(manifest_url_)); |
| DCHECK(!storage_->working_set_.GetCache(cache_record_.cache_id)); |
| CreateCacheAndGroupFromRecords(&cache, &group); |
| } else { |
| group = new AppCacheGroup( |
| storage_->service_, manifest_url_, storage_->NewGroupId()); |
| } |
| } |
| FOR_EACH_DELEGATE(delegates_, OnGroupLoaded(group, manifest_url_)); |
| } |
| |
| // StoreGroupAndCacheTask ------- |
| |
| class AppCacheStorageImpl::StoreGroupAndCacheTask : public StoreOrLoadTask { |
| public: |
| StoreGroupAndCacheTask(AppCacheStorageImpl* storage, AppCacheGroup* group, |
| AppCache* newest_cache); |
| |
| virtual void Run(); |
| virtual void RunCompleted(); |
| virtual void CancelCompletion(); |
| |
| scoped_refptr<AppCacheGroup> group_; |
| scoped_refptr<AppCache> cache_; |
| bool success_; |
| bool would_exceed_quota_; |
| int64 quota_override_; |
| std::vector<int64> newly_deletable_response_ids_; |
| }; |
| |
| AppCacheStorageImpl::StoreGroupAndCacheTask::StoreGroupAndCacheTask( |
| AppCacheStorageImpl* storage, AppCacheGroup* group, AppCache* newest_cache) |
| : StoreOrLoadTask(storage), group_(group), cache_(newest_cache), |
| success_(false), would_exceed_quota_(false), |
| quota_override_( |
| storage->GetOriginQuotaInMemory(group->manifest_url().GetOrigin())) { |
| group_record_.group_id = group->group_id(); |
| group_record_.manifest_url = group->manifest_url(); |
| group_record_.origin = group_record_.manifest_url.GetOrigin(); |
| newest_cache->ToDatabaseRecords( |
| group, |
| &cache_record_, &entry_records_, &fallback_namespace_records_, |
| &online_whitelist_records_); |
| } |
| |
| void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() { |
| DCHECK(!success_); |
| sql::Connection* connection = database_->db_connection(); |
| if (!connection) |
| return; |
| |
| sql::Transaction transaction(connection); |
| if (!transaction.Begin()) |
| return; |
| |
| AppCacheDatabase::GroupRecord existing_group; |
| success_ = database_->FindGroup(group_record_.group_id, &existing_group); |
| if (!success_) { |
| group_record_.creation_time = base::Time::Now(); |
| group_record_.last_access_time = base::Time::Now(); |
| success_ = database_->InsertGroup(&group_record_); |
| } else { |
| DCHECK(group_record_.group_id == existing_group.group_id); |
| DCHECK(group_record_.manifest_url == existing_group.manifest_url); |
| DCHECK(group_record_.origin == existing_group.origin); |
| |
| database_->UpdateGroupLastAccessTime(group_record_.group_id, |
| base::Time::Now()); |
| |
| AppCacheDatabase::CacheRecord cache; |
| if (database_->FindCacheForGroup(group_record_.group_id, &cache)) { |
| // Get the set of response ids in the old cache. |
| std::set<int64> existing_response_ids; |
| database_->FindResponseIdsForCacheAsSet(cache.cache_id, |
| &existing_response_ids); |
| |
| // Remove those that remain in the new cache. |
| std::vector<AppCacheDatabase::EntryRecord>::const_iterator entry_iter = |
| entry_records_.begin(); |
| while (entry_iter != entry_records_.end()) { |
| existing_response_ids.erase(entry_iter->response_id); |
| ++entry_iter; |
| } |
| |
| // The rest are deletable. |
| std::set<int64>::const_iterator id_iter = existing_response_ids.begin(); |
| while (id_iter != existing_response_ids.end()) { |
| newly_deletable_response_ids_.push_back(*id_iter); |
| ++id_iter; |
| } |
| |
| success_ = |
| database_->DeleteCache(cache.cache_id) && |
| database_->DeleteEntriesForCache(cache.cache_id) && |
| database_->DeleteFallbackNameSpacesForCache(cache.cache_id) && |
| database_->DeleteOnlineWhiteListForCache(cache.cache_id) && |
| database_->InsertDeletableResponseIds(newly_deletable_response_ids_); |
| } else { |
| NOTREACHED() << "A existing group without a cache is unexpected"; |
| } |
| } |
| |
| success_ = |
| success_ && |
| database_->InsertCache(&cache_record_) && |
| database_->InsertEntryRecords(entry_records_) && |
| database_->InsertFallbackNameSpaceRecords(fallback_namespace_records_)&& |
| database_->InsertOnlineWhiteListRecords(online_whitelist_records_); |
| |
| if (!success_) |
| return; |
| |
| int64 quota = (quota_override_ >= 0) ? |
| quota_override_ : |
| database_->GetOriginQuota(group_record_.origin); |
| |
| if (database_->GetOriginUsage(group_record_.origin) > quota) { |
| would_exceed_quota_ = true; |
| success_ = false; |
| return; |
| } |
| |
| success_ = transaction.Commit(); |
| } |
| |
| void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() { |
| if (success_) { |
| // TODO(kkanetkar): Add to creation time when that's enabled. |
| storage_->origins_with_groups_.insert(group_->manifest_url().GetOrigin()); |
| if (cache_ != group_->newest_complete_cache()) { |
| cache_->set_complete(true); |
| group_->AddCache(cache_); |
| } |
| group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_); |
| } |
| FOR_EACH_DELEGATE(delegates_, |
| OnGroupAndNewestCacheStored(group_, cache_, success_, |
| would_exceed_quota_)); |
| group_ = NULL; |
| cache_ = NULL; |
| } |
| |
| void AppCacheStorageImpl::StoreGroupAndCacheTask::CancelCompletion() { |
| // Overriden to safely drop our reference to the group and cache |
| // which are not thread safe refcounted. |
| DatabaseTask::CancelCompletion(); |
| group_ = NULL; |
| cache_ = NULL; |
| } |
| |
| // FindMainResponseTask ------- |
| |
| class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask { |
| public: |
| FindMainResponseTask(AppCacheStorageImpl* storage, const GURL& url, |
| const AppCacheWorkingSet::GroupMap* groups_in_use) |
| : DatabaseTask(storage), url_(url), cache_id_(kNoCacheId) { |
| if (groups_in_use) { |
| for (AppCacheWorkingSet::GroupMap::const_iterator it = |
| groups_in_use->begin(); |
| it != groups_in_use->end(); ++it) { |
| AppCacheGroup* group = it->second; |
| AppCache* cache = group->newest_complete_cache(); |
| if (group->is_obsolete() || !cache) |
| continue; |
| cache_ids_in_use_.insert(cache->cache_id()); |
| } |
| } |
| } |
| |
| virtual void Run(); |
| virtual void RunCompleted(); |
| |
| GURL url_; |
| std::set<int64> cache_ids_in_use_; |
| AppCacheEntry entry_; |
| AppCacheEntry fallback_entry_; |
| int64 cache_id_; |
| GURL manifest_url_; |
| }; |
| |
| namespace { |
| bool SortByLength( |
| const AppCacheDatabase::FallbackNameSpaceRecord& lhs, |
| const AppCacheDatabase::FallbackNameSpaceRecord& rhs) { |
| return lhs.namespace_url.spec().length() > rhs.namespace_url.spec().length(); |
| } |
| } |
| |
| void AppCacheStorageImpl::FindMainResponseTask::Run() { |
| // We have a bias for hits from caches that are in use. |
| |
| // TODO(michaeln): The heuristics around choosing amoungst |
| // multiple candidates is under specified, and just plain |
| // not fully understood. Refine these over time. In particular, |
| // * prefer candidates from newer caches |
| // * take into account the cache associated with the document |
| // that initiated the navigation |
| // * take into account the cache associated with the document |
| // currently residing in the frame being navigated |
| |
| // First look for an exact match. We don't worry about whether |
| // the containing cache is in-use in this loop because the |
| // storage class's FindResponseForMainRequest method does that |
| // as a pre-optimization. |
| std::vector<AppCacheDatabase::EntryRecord> entries; |
| if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) { |
| std::vector<AppCacheDatabase::EntryRecord>::iterator iter; |
| for (iter = entries.begin(); iter < entries.end(); ++iter) { |
| if (iter->flags & AppCacheEntry::FOREIGN) |
| continue; |
| |
| AppCacheDatabase::GroupRecord group_record; |
| if (!database_->FindGroupForCache(iter->cache_id, &group_record)) { |
| NOTREACHED() << "A cache without a group is not expected."; |
| continue; |
| } |
| entry_ = AppCacheEntry(iter->flags, iter->response_id); |
| cache_id_ = iter->cache_id; |
| manifest_url_ = group_record.manifest_url; |
| return; |
| } |
| } |
| |
| // No exact matches, look at the fallback namespaces for this origin. |
| std::vector<AppCacheDatabase::FallbackNameSpaceRecord> fallbacks; |
| if (!database_->FindFallbackNameSpacesForOrigin(url_.GetOrigin(), &fallbacks) |
| || fallbacks.empty()) { |
| return; |
| } |
| |
| // Sort by namespace url string length, longest to shortest, |
| // since longer matches trump when matching a url to a namespace. |
| std::sort(fallbacks.begin(), fallbacks.end(), SortByLength); |
| |
| bool has_candidate = false; |
| GURL candidate_fallback_namespace; |
| std::vector<AppCacheDatabase::FallbackNameSpaceRecord>::iterator iter; |
| for (iter = fallbacks.begin(); iter < fallbacks.end(); ++iter) { |
| if (has_candidate && |
| (candidate_fallback_namespace.spec().length() > |
| iter->namespace_url.spec().length())) { |
| break; // Stop iterating since longer namespace prefix matches win. |
| } |
| |
| if (StartsWithASCII(url_.spec(), iter->namespace_url.spec(), true)) { |
| bool is_cache_in_use = cache_ids_in_use_.find(iter->cache_id) != |
| cache_ids_in_use_.end(); |
| |
| bool take_new_candidate = !has_candidate || is_cache_in_use; |
| |
| AppCacheDatabase::EntryRecord entry_record; |
| if (take_new_candidate && |
| database_->FindEntry(iter->cache_id, iter->fallback_entry_url, |
| &entry_record)) { |
| AppCacheDatabase::GroupRecord group_record; |
| if (!database_->FindGroupForCache(iter->cache_id, &group_record)) { |
| NOTREACHED() << "A cache without a group is not expected."; |
| continue; |
| } |
| cache_id_ = iter->cache_id; |
| manifest_url_ = group_record.manifest_url; |
| fallback_entry_ = AppCacheEntry( |
| entry_record.flags, entry_record.response_id); |
| if (is_cache_in_use) |
| break; // Stop iterating since we favor hits from in-use caches. |
| candidate_fallback_namespace = iter->namespace_url; |
| has_candidate = true; |
| } |
| } |
| } |
| } |
| |
| void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() { |
| storage_->CheckPolicyAndCallOnMainResponseFound( |
| &delegates_, url_, entry_, fallback_entry_, cache_id_, manifest_url_); |
| } |
| |
| // MarkEntryAsForeignTask ------- |
| |
| class AppCacheStorageImpl::MarkEntryAsForeignTask : public DatabaseTask { |
| public: |
| MarkEntryAsForeignTask( |
| AppCacheStorageImpl* storage, const GURL& url, int64 cache_id) |
| : DatabaseTask(storage), cache_id_(cache_id), entry_url_(url) {} |
| |
| virtual void Run(); |
| virtual void RunCompleted(); |
| |
| int64 cache_id_; |
| GURL entry_url_; |
| }; |
| |
| void AppCacheStorageImpl::MarkEntryAsForeignTask::Run() { |
| database_->AddEntryFlags(entry_url_, cache_id_, AppCacheEntry::FOREIGN); |
| } |
| |
| void AppCacheStorageImpl::MarkEntryAsForeignTask::RunCompleted() { |
| DCHECK(storage_->pending_foreign_markings_.front().first == entry_url_ && |
| storage_->pending_foreign_markings_.front().second == cache_id_); |
| storage_->pending_foreign_markings_.pop_front(); |
| } |
| |
| // MakeGroupObsoleteTask ------- |
| |
| class AppCacheStorageImpl::MakeGroupObsoleteTask : public DatabaseTask { |
| public: |
| MakeGroupObsoleteTask(AppCacheStorageImpl* storage, AppCacheGroup* group); |
| |
| virtual void Run(); |
| virtual void RunCompleted(); |
| virtual void CancelCompletion(); |
| |
| scoped_refptr<AppCacheGroup> group_; |
| int64 group_id_; |
| bool success_; |
| std::set<GURL> origins_with_groups_; |
| std::vector<int64> newly_deletable_response_ids_; |
| }; |
| |
| AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask( |
| AppCacheStorageImpl* storage, AppCacheGroup* group) |
| : DatabaseTask(storage), group_(group), group_id_(group->group_id()), |
| success_(false) { |
| } |
| |
| void AppCacheStorageImpl::MakeGroupObsoleteTask::Run() { |
| DCHECK(!success_); |
| sql::Connection* connection = database_->db_connection(); |
| if (!connection) |
| return; |
| |
| sql::Transaction transaction(connection); |
| if (!transaction.Begin()) |
| return; |
| |
| AppCacheDatabase::GroupRecord group_record; |
| if (!database_->FindGroup(group_id_, &group_record)) { |
| // This group doesn't exists in the database, nothing todo here. |
| success_ = true; |
| return; |
| } |
| |
| AppCacheDatabase::CacheRecord cache_record; |
| if (database_->FindCacheForGroup(group_id_, &cache_record)) { |
| database_->FindResponseIdsForCacheAsVector(cache_record.cache_id, |
| &newly_deletable_response_ids_); |
| success_ = |
| database_->DeleteGroup(group_id_) && |
| database_->DeleteCache(cache_record.cache_id) && |
| database_->DeleteEntriesForCache(cache_record.cache_id) && |
| database_->DeleteFallbackNameSpacesForCache(cache_record.cache_id) && |
| database_->DeleteOnlineWhiteListForCache(cache_record.cache_id) && |
| database_->InsertDeletableResponseIds(newly_deletable_response_ids_); |
| } else { |
| NOTREACHED() << "A existing group without a cache is unexpected"; |
| success_ = database_->DeleteGroup(group_id_); |
| } |
| |
| success_ = success_ && |
| database_->FindOriginsWithGroups(&origins_with_groups_) && |
| transaction.Commit(); |
| } |
| |
| void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() { |
| if (success_) { |
| group_->set_obsolete(true); |
| if (!storage_->is_disabled()) { |
| storage_->origins_with_groups_.swap(origins_with_groups_); |
| group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_); |
| |
| // Also remove from the working set, caches for an 'obsolete' group |
| // may linger in use, but the group itself cannot be looked up by |
| // 'manifest_url' in the working set any longer. |
| storage_->working_set()->RemoveGroup(group_); |
| } |
| } |
| FOR_EACH_DELEGATE(delegates_, OnGroupMadeObsolete(group_, success_)); |
| group_ = NULL; |
| } |
| |
| void AppCacheStorageImpl::MakeGroupObsoleteTask::CancelCompletion() { |
| // Overriden to safely drop our reference to the group |
| // which is not thread safe refcounted. |
| DatabaseTask::CancelCompletion(); |
| group_ = NULL; |
| } |
| |
| // GetDeletableResponseIdsTask ------- |
| |
| class AppCacheStorageImpl::GetDeletableResponseIdsTask : public DatabaseTask { |
| public: |
| GetDeletableResponseIdsTask(AppCacheStorageImpl* storage, int64 max_rowid) |
| : DatabaseTask(storage), max_rowid_(max_rowid) {} |
| |
| virtual void Run(); |
| virtual void RunCompleted(); |
| |
| int64 max_rowid_; |
| std::vector<int64> response_ids_; |
| }; |
| |
| void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() { |
| const int kSqlLimit = 1000; |
| database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit); |
| } |
| |
| void AppCacheStorageImpl::GetDeletableResponseIdsTask::RunCompleted() { |
| if (!response_ids_.empty()) |
| storage_->StartDeletingResponses(response_ids_); |
| } |
| |
| // InsertDeletableResponseIdsTask ------- |
| |
| class AppCacheStorageImpl::InsertDeletableResponseIdsTask |
| : public DatabaseTask { |
| public: |
| explicit InsertDeletableResponseIdsTask(AppCacheStorageImpl* storage) |
| : DatabaseTask(storage) {} |
| virtual void Run(); |
| std::vector<int64> response_ids_; |
| }; |
| |
| void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() { |
| database_->InsertDeletableResponseIds(response_ids_); |
| } |
| |
| // DeleteDeletableResponseIdsTask ------- |
| |
| class AppCacheStorageImpl::DeleteDeletableResponseIdsTask |
| : public DatabaseTask { |
| public: |
| explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage) |
| : DatabaseTask(storage) {} |
| virtual void Run(); |
| std::vector<int64> response_ids_; |
| }; |
| |
| void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() { |
| database_->DeleteDeletableResponseIds(response_ids_); |
| } |
| |
| // UpdateGroupLastAccessTimeTask ------- |
| |
| class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask |
| : public DatabaseTask { |
| public: |
| UpdateGroupLastAccessTimeTask( |
| AppCacheStorageImpl* storage, int64 group_id, base::Time time) |
| : DatabaseTask(storage), group_id_(group_id), last_access_time_(time) {} |
| |
| virtual void Run(); |
| |
| int64 group_id_; |
| base::Time last_access_time_; |
| }; |
| |
| void AppCacheStorageImpl::UpdateGroupLastAccessTimeTask::Run() { |
| database_->UpdateGroupLastAccessTime(group_id_, last_access_time_); |
| } |
| |
| |
| // AppCacheStorageImpl --------------------------------------------------- |
| |
| AppCacheStorageImpl::AppCacheStorageImpl(AppCacheService* service) |
| : AppCacheStorage(service), is_incognito_(false), |
| is_response_deletion_scheduled_(false), |
| did_start_deleting_responses_(false), |
| last_deletable_response_rowid_(0), |
| ALLOW_THIS_IN_INITIALIZER_LIST(doom_callback_( |
| this, &AppCacheStorageImpl::OnDeletedOneResponse)), |
| ALLOW_THIS_IN_INITIALIZER_LIST(init_callback_( |
| this, &AppCacheStorageImpl::OnDiskCacheInitialized)), |
| database_(NULL), is_disabled_(false), |
| ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { |
| } |
| |
| AppCacheStorageImpl::~AppCacheStorageImpl() { |
| STLDeleteElements(&pending_simple_tasks_); |
| |
| std::for_each(scheduled_database_tasks_.begin(), |
| scheduled_database_tasks_.end(), |
| std::mem_fun(&DatabaseTask::CancelCompletion)); |
| |
| if (database_) |
| AppCacheThread::DeleteSoon(AppCacheThread::db(), FROM_HERE, database_); |
| } |
| |
| void AppCacheStorageImpl::Initialize(const FilePath& cache_directory, |
| base::MessageLoopProxy* cache_thread) { |
| cache_directory_ = cache_directory; |
| cache_thread_ = cache_thread; |
| is_incognito_ = cache_directory_.empty(); |
| |
| FilePath db_file_path; |
| if (!is_incognito_) |
| db_file_path = cache_directory_.Append(kAppCacheDatabaseName); |
| database_ = new AppCacheDatabase(db_file_path); |
| |
| scoped_refptr<InitTask> task = new InitTask(this); |
| task->Schedule(); |
| } |
| |
| void AppCacheStorageImpl::Disable() { |
| if (is_disabled_) |
| return; |
| LOG(INFO) << "Disabling appcache storage."; |
| is_disabled_ = true; |
| origins_with_groups_.clear(); |
| working_set()->Disable(); |
| if (disk_cache_.get()) |
| disk_cache_->Disable(); |
| scoped_refptr<DisableDatabaseTask> task = new DisableDatabaseTask(this); |
| task->Schedule(); |
| } |
| |
| void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) { |
| DCHECK(delegate); |
| scoped_refptr<GetAllInfoTask> task = new GetAllInfoTask(this); |
| task->AddDelegate(GetOrCreateDelegateReference(delegate)); |
| task->Schedule(); |
| } |
| |
| void AppCacheStorageImpl::LoadCache(int64 id, Delegate* delegate) { |
| DCHECK(delegate); |
| if (is_disabled_) { |
| delegate->OnCacheLoaded(NULL, id); |
| return; |
| } |
| |
| AppCache* cache = working_set_.GetCache(id); |
| if (cache) { |
| delegate->OnCacheLoaded(cache, id); |
| if (cache->owning_group()) { |
| scoped_refptr<DatabaseTask> update_task = |
| new UpdateGroupLastAccessTimeTask( |
| this, cache->owning_group()->group_id(), base::Time::Now()); |
| update_task->Schedule(); |
| } |
| return; |
| } |
| scoped_refptr<CacheLoadTask> task = GetPendingCacheLoadTask(id); |
| if (task) { |
| task->AddDelegate(GetOrCreateDelegateReference(delegate)); |
| return; |
| } |
| task = new CacheLoadTask(id, this); |
| task->AddDelegate(GetOrCreateDelegateReference(delegate)); |
| task->Schedule(); |
| pending_cache_loads_[id] = task; |
| } |
| |
| void AppCacheStorageImpl::LoadOrCreateGroup( |
| const GURL& manifest_url, Delegate* delegate) { |
| DCHECK(delegate); |
| if (is_disabled_) { |
| delegate->OnGroupLoaded(NULL, manifest_url); |
| return; |
| } |
| |
| AppCacheGroup* group = working_set_.GetGroup(manifest_url); |
| if (group) { |
| delegate->OnGroupLoaded(group, manifest_url); |
| scoped_refptr<DatabaseTask> update_task = |
| new UpdateGroupLastAccessTimeTask( |
| this, group->group_id(), base::Time::Now()); |
| update_task->Schedule(); |
| return; |
| } |
| |
| scoped_refptr<GroupLoadTask> task = GetPendingGroupLoadTask(manifest_url); |
| if (task) { |
| task->AddDelegate(GetOrCreateDelegateReference(delegate)); |
| return; |
| } |
| |
| if (origins_with_groups_.find(manifest_url.GetOrigin()) == |
| origins_with_groups_.end()) { |
| // No need to query the database, return a new group immediately. |
| scoped_refptr<AppCacheGroup> group = new AppCacheGroup( |
| service_, manifest_url, NewGroupId()); |
| delegate->OnGroupLoaded(group, manifest_url); |
| return; |
| } |
| |
| task = new GroupLoadTask(manifest_url, this); |
| task->AddDelegate(GetOrCreateDelegateReference(delegate)); |
| task->Schedule(); |
| pending_group_loads_[manifest_url] = task.get(); |
| } |
| |
| void AppCacheStorageImpl::StoreGroupAndNewestCache( |
| AppCacheGroup* group, AppCache* newest_cache, Delegate* delegate) { |
| // TODO(michaeln): distinguish between a simple update of an existing |
| // cache that just adds new master entry(s), and the insertion of a |
| // whole new cache. The StoreGroupAndCacheTask as written will handle |
| // the simple update case in a very heavy weight way (delete all and |
| // the reinsert all over again). |
| DCHECK(group && delegate && newest_cache); |
| scoped_refptr<StoreGroupAndCacheTask> task = |
| new StoreGroupAndCacheTask(this, group, newest_cache); |
| task->AddDelegate(GetOrCreateDelegateReference(delegate)); |
| task->Schedule(); |
| } |
| |
| void AppCacheStorageImpl::FindResponseForMainRequest( |
| const GURL& url, Delegate* delegate) { |
| DCHECK(delegate); |
| |
| const GURL* url_ptr = &url; |
| GURL url_no_ref; |
| if (url.has_ref()) { |
| GURL::Replacements replacements; |
| replacements.ClearRef(); |
| url_no_ref = url.ReplaceComponents(replacements); |
| url_ptr = &url_no_ref; |
| } |
| |
| const GURL origin = url.GetOrigin(); |
| |
| // First look in our working set for a direct hit without having to query |
| // the database. |
| const AppCacheWorkingSet::GroupMap* groups_in_use = |
| working_set()->GetGroupsInOrigin(origin); |
| if (groups_in_use) { |
| for (AppCacheWorkingSet::GroupMap::const_iterator it = |
| groups_in_use->begin(); |
| it != groups_in_use->end(); ++it) { |
| AppCacheGroup* group = it->second; |
| AppCache* cache = group->newest_complete_cache(); |
| if (group->is_obsolete() || !cache) |
| continue; |
| |
| AppCacheEntry* entry = cache->GetEntry(*url_ptr); |
| if (entry && !entry->IsForeign()) { |
| ScheduleSimpleTask(method_factory_.NewRunnableMethod( |
| &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse, |
| url, *entry, make_scoped_refptr(group), make_scoped_refptr(cache), |
| make_scoped_refptr(GetOrCreateDelegateReference(delegate)))); |
| return; |
| } |
| } |
| } |
| |
| if (IsInitTaskComplete() && |
| origins_with_groups_.find(origin) == origins_with_groups_.end()) { |
| // No need to query the database, return async'ly but without going thru |
| // the DB thread. |
| scoped_refptr<AppCacheGroup> no_group; |
| scoped_refptr<AppCache> no_cache; |
| ScheduleSimpleTask(method_factory_.NewRunnableMethod( |
| &AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse, |
| url, AppCacheEntry(), no_group, no_cache, |
| make_scoped_refptr(GetOrCreateDelegateReference(delegate)))); |
| return; |
| } |
| |
| // We have to query the database, schedule a database task to do so. |
| scoped_refptr<FindMainResponseTask> task = |
| new FindMainResponseTask(this, *url_ptr, groups_in_use); |
| task->AddDelegate(GetOrCreateDelegateReference(delegate)); |
| task->Schedule(); |
| } |
| |
| void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse( |
| const GURL& url, AppCacheEntry found_entry, |
| scoped_refptr<AppCacheGroup> group, scoped_refptr<AppCache> cache, |
| scoped_refptr<DelegateReference> delegate_ref) { |
| if (delegate_ref->delegate) { |
| DelegateReferenceVector delegates(1, delegate_ref); |
| CheckPolicyAndCallOnMainResponseFound( |
| &delegates, url, found_entry, AppCacheEntry(), |
| cache.get() ? cache->cache_id() : kNoCacheId, |
| group.get() ? group->manifest_url() : GURL()); |
| } |
| } |
| |
| void AppCacheStorageImpl::CheckPolicyAndCallOnMainResponseFound( |
| DelegateReferenceVector* delegates, const GURL& url, |
| const AppCacheEntry& entry, const AppCacheEntry& fallback_entry, |
| int64 cache_id, const GURL& manifest_url) { |
| if (!manifest_url.is_empty()) { |
| // Check the policy prior to returning a main resource from the appcache. |
| AppCachePolicy* policy = service()->appcache_policy(); |
| if (policy && !policy->CanLoadAppCache(manifest_url)) { |
| FOR_EACH_DELEGATE( |
| (*delegates), |
| OnMainResponseFound(url, AppCacheEntry(), AppCacheEntry(), |
| kNoCacheId, manifest_url, true)); |
| return; |
| } |
| } |
| |
| FOR_EACH_DELEGATE( |
| (*delegates), |
| OnMainResponseFound(url, entry, fallback_entry, |
| cache_id, manifest_url, false)); |
| } |
| |
| void AppCacheStorageImpl::FindResponseForSubRequest( |
| AppCache* cache, const GURL& url, |
| AppCacheEntry* found_entry, AppCacheEntry* found_fallback_entry, |
| bool* found_network_namespace) { |
| DCHECK(cache && cache->is_complete()); |
| |
| // When a group is forcibly deleted, all subresource loads for pages |
| // using caches in the group will result in a synthesized network errors. |
| // Forcible deletion is not a function that is covered by the HTML5 spec. |
| if (cache->owning_group()->is_being_deleted()) { |
| *found_entry = AppCacheEntry(); |
| *found_fallback_entry = AppCacheEntry(); |
| *found_network_namespace = false; |
| return; |
| } |
| |
| GURL fallback_namespace_not_used; |
| cache->FindResponseForRequest( |
| url, found_entry, found_fallback_entry, |
| &fallback_namespace_not_used, found_network_namespace); |
| } |
| |
| void AppCacheStorageImpl::MarkEntryAsForeign( |
| const GURL& entry_url, int64 cache_id) { |
| AppCache* cache = working_set_.GetCache(cache_id); |
| if (cache) { |
| AppCacheEntry* entry = cache->GetEntry(entry_url); |
| DCHECK(entry); |
| if (entry) |
| entry->add_types(AppCacheEntry::FOREIGN); |
| } |
| scoped_refptr<MarkEntryAsForeignTask> task = |
| new MarkEntryAsForeignTask(this, entry_url, cache_id); |
| task->Schedule(); |
| pending_foreign_markings_.push_back(std::make_pair(entry_url, cache_id)); |
| } |
| |
| void AppCacheStorageImpl::MakeGroupObsolete( |
| AppCacheGroup* group, Delegate* delegate) { |
| DCHECK(group && delegate); |
| scoped_refptr<MakeGroupObsoleteTask> task = |
| new MakeGroupObsoleteTask(this, group); |
| task->AddDelegate(GetOrCreateDelegateReference(delegate)); |
| task->Schedule(); |
| } |
| |
| AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader( |
| const GURL& manifest_url, int64 response_id) { |
| return new AppCacheResponseReader(response_id, disk_cache()); |
| } |
| |
| AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter( |
| const GURL& manifest_url) { |
| return new AppCacheResponseWriter(NewResponseId(), disk_cache()); |
| } |
| |
| void AppCacheStorageImpl::DoomResponses( |
| const GURL& manifest_url, const std::vector<int64>& response_ids) { |
| if (response_ids.empty()) |
| return; |
| |
| // Start deleting them from the disk cache lazily. |
| StartDeletingResponses(response_ids); |
| |
| // Also schedule a database task to record these ids in the |
| // deletable responses table. |
| // TODO(michaeln): There is a race here. If the browser crashes |
| // prior to committing these rows to the database and prior to us |
| // having deleted them from the disk cache, we'll never delete them. |
| scoped_refptr<InsertDeletableResponseIdsTask> task = |
| new InsertDeletableResponseIdsTask(this); |
| task->response_ids_ = response_ids; |
| task->Schedule(); |
| } |
| |
| void AppCacheStorageImpl::DeleteResponses( |
| const GURL& manifest_url, const std::vector<int64>& response_ids) { |
| if (response_ids.empty()) |
| return; |
| StartDeletingResponses(response_ids); |
| } |
| |
| void AppCacheStorageImpl::PurgeMemory() { |
| scoped_refptr<CloseConnectionTask> task = new CloseConnectionTask(this); |
| task->Schedule(); |
| } |
| |
| void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() { |
| // Only if we haven't already begun. |
| if (!did_start_deleting_responses_) { |
| scoped_refptr<GetDeletableResponseIdsTask> task = |
| new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_); |
| task->Schedule(); |
| } |
| } |
| |
| void AppCacheStorageImpl::StartDeletingResponses( |
| const std::vector<int64>& response_ids) { |
| DCHECK(!response_ids.empty()); |
| did_start_deleting_responses_ = true; |
| deletable_response_ids_.insert( |
| deletable_response_ids_.end(), |
| response_ids.begin(), response_ids.end()); |
| if (!is_response_deletion_scheduled_) |
| ScheduleDeleteOneResponse(); |
| } |
| |
| void AppCacheStorageImpl::ScheduleDeleteOneResponse() { |
| DCHECK(!is_response_deletion_scheduled_); |
| const int kDelayMillis = 10; |
| MessageLoop::current()->PostDelayedTask(FROM_HERE, |
| method_factory_.NewRunnableMethod( |
| &AppCacheStorageImpl::DeleteOneResponse), |
| kDelayMillis); |
| is_response_deletion_scheduled_ = true; |
| } |
| |
| void AppCacheStorageImpl::DeleteOneResponse() { |
| DCHECK(is_response_deletion_scheduled_); |
| DCHECK(!deletable_response_ids_.empty()); |
| |
| if (!disk_cache()) { |
| DCHECK(is_disabled_); |
| deletable_response_ids_.clear(); |
| deleted_response_ids_.clear(); |
| is_response_deletion_scheduled_ = false; |
| return; |
| } |
| |
| int64 id = deletable_response_ids_.front(); |
| int rv = disk_cache_->DoomEntry(id, &doom_callback_); |
| if (rv != net::ERR_IO_PENDING) |
| OnDeletedOneResponse(rv); |
| } |
| |
| void AppCacheStorageImpl::OnDeletedOneResponse(int rv) { |
| is_response_deletion_scheduled_ = false; |
| if (is_disabled_) |
| return; |
| |
| int64 id = deletable_response_ids_.front(); |
| deletable_response_ids_.pop_front(); |
| if (rv != net::ERR_ABORTED) |
| deleted_response_ids_.push_back(id); |
| |
| const size_t kBatchSize = 50U; |
| if (deleted_response_ids_.size() >= kBatchSize || |
| deletable_response_ids_.empty()) { |
| scoped_refptr<DeleteDeletableResponseIdsTask> task = |
| new DeleteDeletableResponseIdsTask(this); |
| task->response_ids_.swap(deleted_response_ids_); |
| task->Schedule(); |
| } |
| |
| if (deletable_response_ids_.empty()) { |
| scoped_refptr<GetDeletableResponseIdsTask> task = |
| new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_); |
| task->Schedule(); |
| return; |
| } |
| |
| ScheduleDeleteOneResponse(); |
| } |
| |
| AppCacheStorageImpl::CacheLoadTask* |
| AppCacheStorageImpl::GetPendingCacheLoadTask(int64 cache_id) { |
| PendingCacheLoads::iterator found = pending_cache_loads_.find(cache_id); |
| if (found != pending_cache_loads_.end()) |
| return found->second; |
| return NULL; |
| } |
| |
| AppCacheStorageImpl::GroupLoadTask* |
| AppCacheStorageImpl::GetPendingGroupLoadTask(const GURL& manifest_url) { |
| PendingGroupLoads::iterator found = pending_group_loads_.find(manifest_url); |
| if (found != pending_group_loads_.end()) |
| return found->second; |
| return NULL; |
| } |
| |
| void AppCacheStorageImpl::GetPendingForeignMarkingsForCache( |
| int64 cache_id, std::vector<GURL>* urls) { |
| PendingForeignMarkings::iterator iter = pending_foreign_markings_.begin(); |
| while (iter != pending_foreign_markings_.end()) { |
| if (iter->second == cache_id) |
| urls->push_back(iter->first); |
| ++iter; |
| } |
| } |
| |
| void AppCacheStorageImpl::ScheduleSimpleTask(Task* task) { |
| pending_simple_tasks_.push_back(task); |
| MessageLoop::current()->PostTask(FROM_HERE, |
| method_factory_.NewRunnableMethod( |
| &AppCacheStorageImpl::RunOnePendingSimpleTask)); |
| } |
| |
| void AppCacheStorageImpl::RunOnePendingSimpleTask() { |
| DCHECK(!pending_simple_tasks_.empty()); |
| Task* task = pending_simple_tasks_.front(); |
| pending_simple_tasks_.pop_front(); |
| task->Run(); |
| delete task; |
| } |
| |
| AppCacheDiskCache* AppCacheStorageImpl::disk_cache() { |
| DCHECK(IsInitTaskComplete()); |
| |
| if (is_disabled_) |
| return NULL; |
| |
| if (!disk_cache_.get()) { |
| int rv = net::OK; |
| disk_cache_.reset(new AppCacheDiskCache); |
| if (is_incognito_) { |
| rv = disk_cache_->InitWithMemBackend( |
| kMaxMemDiskCacheSize, &init_callback_); |
| } else { |
| rv = disk_cache_->InitWithDiskBackend( |
| cache_directory_.Append(kDiskCacheDirectoryName), |
| kMaxDiskCacheSize, false, cache_thread_, &init_callback_); |
| } |
| |
| // We should not keep this reference around. |
| cache_thread_ = NULL; |
| |
| if (rv != net::ERR_IO_PENDING) |
| OnDiskCacheInitialized(rv); |
| } |
| return disk_cache_.get(); |
| } |
| |
| void AppCacheStorageImpl::OnDiskCacheInitialized(int rv) { |
| if (rv != net::OK) { |
| LOG(ERROR) << "Failed to open the appcache diskcache."; |
| AppCacheHistograms::CountInitResult(AppCacheHistograms::DISK_CACHE_ERROR); |
| |
| // We're unable to open the disk cache, this is a fatal error that we can't |
| // really recover from. We handle it by disabling the appcache for this |
| // browser session and deleting the directory on disk. The next browser |
| // session should start with a clean slate. |
| Disable(); |
| if (!is_incognito_) { |
| LOG(INFO) << "Deleting existing appcache data and starting over."; |
| AppCacheThread::PostTask(AppCacheThread::db(), FROM_HERE, |
| NewRunnableFunction(DeleteDirectory, cache_directory_)); |
| } |
| } |
| } |
| |
| } // namespace appcache |