// Copyright (c) 2012 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 #include #include #include #include "base/bind.h" #include "base/bind_helpers.h" #include "base/file_util.h" #include "base/logging.h" #include "base/message_loop.h" #include "base/stl_util.h" #include "base/string_util.h" #include "net/base/cache_type.h" #include "net/base/net_errors.h" #include "sql/connection.h" #include "sql/transaction.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_quota_client.h" #include "webkit/appcache/appcache_response.h" #include "webkit/appcache/appcache_service.h" #include "webkit/quota/quota_client.h" #include "webkit/quota/quota_manager.h" #include "webkit/quota/special_storage_policy.h" namespace appcache { // Hard coded default when not using quota management. static const int kDefaultQuota = 5 * 1024 * 1024; static const int kMaxDiskCacheSize = 250 * 1024 * 1024; static const int kMaxMemDiskCacheSize = 10 * 1024 * 1024; static const base::FilePath::CharType kDiskCacheDirectoryName[] = FILE_PATH_LITERAL("Cache"); namespace { // Helpers for clearing data from the AppCacheDatabase. bool DeleteGroupAndRelatedRecords(AppCacheDatabase* database, int64 group_id, std::vector* deletable_response_ids) { AppCacheDatabase::CacheRecord cache_record; bool success = false; if (database->FindCacheForGroup(group_id, &cache_record)) { database->FindResponseIdsForCacheAsVector(cache_record.cache_id, deletable_response_ids); success = database->DeleteGroup(group_id) && database->DeleteCache(cache_record.cache_id) && database->DeleteEntriesForCache(cache_record.cache_id) && database->DeleteNamespacesForCache(cache_record.cache_id) && database->DeleteOnlineWhiteListForCache(cache_record.cache_id) && database->InsertDeletableResponseIds(*deletable_response_ids); } else { NOTREACHED() << "A existing group without a cache is unexpected"; success = database->DeleteGroup(group_id); } return success; } // Destroys |database|. If there is appcache data to be deleted // (|force_keep_session_state| is false), deletes session-only appcache data. void ClearSessionOnlyOrigins( AppCacheDatabase* database, scoped_refptr special_storage_policy, bool force_keep_session_state) { scoped_ptr database_to_delete(database); // If saving session state, only delete the database. if (force_keep_session_state) return; bool has_session_only_appcaches = special_storage_policy.get() && special_storage_policy->HasSessionOnlyOrigins(); // Clearning only session-only databases, and there are none. if (!has_session_only_appcaches) return; std::set origins; database->FindOriginsWithGroups(&origins); if (origins.empty()) return; // nothing to delete sql::Connection* connection = database->db_connection(); if (!connection) { NOTREACHED() << "Missing database connection."; return; } std::set::const_iterator origin; for (origin = origins.begin(); origin != origins.end(); ++origin) { if (!special_storage_policy->IsStorageSessionOnly(*origin)) continue; if (special_storage_policy && special_storage_policy->IsStorageProtected(*origin)) continue; std::vector groups; database->FindGroupsForOrigin(*origin, &groups); std::vector::const_iterator group; for (group = groups.begin(); group != groups.end(); ++group) { sql::Transaction transaction(connection); if (!transaction.Begin()) { NOTREACHED() << "Failed to start transaction"; return; } std::vector deletable_response_ids; bool success = DeleteGroupAndRelatedRecords(database, group->group_id, &deletable_response_ids); success = success && transaction.Commit(); DCHECK(success); } // for each group } // for each origin } } // namespace // DatabaseTask ----------------------------------------- class AppCacheStorageImpl::DatabaseTask : public base::RefCountedThreadSafe { public: explicit DatabaseTask(AppCacheStorageImpl* storage) : storage_(storage), database_(storage->database_), io_thread_(base::MessageLoopProxy::current()) { DCHECK(io_thread_); } void AddDelegate(DelegateReference* delegate_reference) { delegates_.push_back(make_scoped_refptr(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: friend class base::RefCountedThreadSafe; virtual ~DatabaseTask() {} AppCacheStorageImpl* storage_; AppCacheDatabase* database_; DelegateReferenceVector delegates_; private: void CallRun(base::TimeTicks schedule_time); void CallRunCompleted(base::TimeTicks schedule_time); void CallDisableStorage(); scoped_refptr io_thread_; }; void AppCacheStorageImpl::DatabaseTask::Schedule() { DCHECK(storage_); DCHECK(io_thread_->BelongsToCurrentThread()); if (storage_->db_thread_->PostTask( FROM_HERE, base::Bind(&DatabaseTask::CallRun, this, base::TimeTicks::Now()))) { storage_->scheduled_database_tasks_.push_back(this); } else { NOTREACHED() << "The database thread is not running."; } } void AppCacheStorageImpl::DatabaseTask::CancelCompletion() { DCHECK(io_thread_->BelongsToCurrentThread()); delegates_.clear(); storage_ = NULL; } void AppCacheStorageImpl::DatabaseTask::CallRun( base::TimeTicks schedule_time) { AppCacheHistograms::AddTaskQueueTimeSample( base::TimeTicks::Now() - schedule_time); if (!database_->is_disabled()) { base::TimeTicks run_time = base::TimeTicks::Now(); Run(); AppCacheHistograms::AddTaskRunTimeSample( base::TimeTicks::Now() - run_time); if (database_->is_disabled()) { io_thread_->PostTask( FROM_HERE, base::Bind(&DatabaseTask::CallDisableStorage, this)); } } io_thread_->PostTask( FROM_HERE, base::Bind(&DatabaseTask::CallRunCompleted, this, base::TimeTicks::Now())); } void AppCacheStorageImpl::DatabaseTask::CallRunCompleted( base::TimeTicks schedule_time) { AppCacheHistograms::AddCompletionQueueTimeSample( base::TimeTicks::Now() - schedule_time); if (storage_) { DCHECK(io_thread_->BelongsToCurrentThread()); DCHECK(storage_->scheduled_database_tasks_.front() == this); storage_->scheduled_database_tasks_.pop_front(); base::TimeTicks run_time = base::TimeTicks::Now(); RunCompleted(); AppCacheHistograms::AddCompletionRunTimeSample( base::TimeTicks::Now() - run_time); delegates_.clear(); } } void AppCacheStorageImpl::DatabaseTask::CallDisableStorage() { if (storage_) { DCHECK(io_thread_->BelongsToCurrentThread()); 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) {} // DatabaseTask: virtual void Run() OVERRIDE; virtual void RunCompleted() OVERRIDE; protected: virtual ~InitTask() {} private: int64 last_group_id_; int64 last_cache_id_; int64 last_response_id_; int64 last_deletable_response_rowid_; std::map usage_map_; }; void AppCacheStorageImpl::InitTask::Run() { database_->FindLastStorageIds( &last_group_id_, &last_cache_id_, &last_response_id_, &last_deletable_response_rowid_); database_->GetAllOriginUsage(&usage_map_); } 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_->usage_map_.swap(usage_map_); const base::TimeDelta kDelay = base::TimeDelta::FromMinutes(5); MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&AppCacheStorageImpl::DelayedStartDeletingUnusedResponses, storage_->weak_factory_.GetWeakPtr()), kDelay); } if (storage_->service()->quota_client()) storage_->service()->quota_client()->NotifyAppCacheReady(); } // CloseConnectionTask ------- class AppCacheStorageImpl::CloseConnectionTask : public DatabaseTask { public: explicit CloseConnectionTask(AppCacheStorageImpl* storage) : DatabaseTask(storage) {} // DatabaseTask: virtual void Run() OVERRIDE { database_->CloseConnection(); } protected: virtual ~CloseConnectionTask() {} }; // DisableDatabaseTask ------- class AppCacheStorageImpl::DisableDatabaseTask : public DatabaseTask { public: explicit DisableDatabaseTask(AppCacheStorageImpl* storage) : DatabaseTask(storage) {} // DatabaseTask: virtual void Run() OVERRIDE { database_->Disable(); } protected: virtual ~DisableDatabaseTask() {} }; // GetAllInfoTask ------- class AppCacheStorageImpl::GetAllInfoTask : public DatabaseTask { public: explicit GetAllInfoTask(AppCacheStorageImpl* storage) : DatabaseTask(storage), info_collection_(new AppCacheInfoCollection()) { } // DatabaseTask: virtual void Run() OVERRIDE; virtual void RunCompleted() OVERRIDE; protected: virtual ~GetAllInfoTask() {} private: scoped_refptr info_collection_; }; void AppCacheStorageImpl::GetAllInfoTask::Run() { std::set origins; database_->FindOriginsWithGroups(&origins); for (std::set::const_iterator origin = origins.begin(); origin != origins.end(); ++origin) { AppCacheInfoVector& infos = info_collection_->infos_by_origin[*origin]; std::vector groups; database_->FindGroupsForOrigin(*origin, &groups); for (std::vector::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.group_id = group->group_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) {} virtual ~StoreOrLoadTask() {} bool FindRelatedCacheRecords(int64 cache_id); void CreateCacheAndGroupFromRecords( scoped_refptr* cache, scoped_refptr* group); AppCacheDatabase::GroupRecord group_record_; AppCacheDatabase::CacheRecord cache_record_; std::vector entry_records_; std::vector intercept_namespace_records_; std::vector fallback_namespace_records_; std::vector online_whitelist_records_; }; bool AppCacheStorageImpl::StoreOrLoadTask::FindRelatedCacheRecords( int64 cache_id) { return database_->FindEntriesForCache(cache_id, &entry_records_) && database_->FindNamespacesForCache( cache_id, &intercept_namespace_records_, &fallback_namespace_records_) && database_->FindOnlineWhiteListForCache( cache_id, &online_whitelist_records_); } void AppCacheStorageImpl::StoreOrLoadTask::CreateCacheAndGroupFromRecords( scoped_refptr* cache, scoped_refptr* group) { DCHECK(storage_ && cache && group); (*cache) = storage_->working_set_.GetCache(cache_record_.cache_id); if (cache->get()) { (*group) = cache->get()->owning_group(); DCHECK(group->get()); DCHECK_EQ(group_record_.group_id, group->get()->group_id()); // TODO(michaeln): histogram is fishing for clues to crbug/95101 if (!cache->get()->GetEntry(group_record_.manifest_url)) { AppCacheHistograms::AddMissingManifestDetectedAtCallsite( AppCacheHistograms::CALLSITE_0); } storage_->NotifyStorageAccessed(group_record_.origin); return; } (*cache) = new AppCache(storage_, cache_record_.cache_id); cache->get()->InitializeWithDatabaseRecords( cache_record_, entry_records_, intercept_namespace_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()); // TODO(michaeln): histogram is fishing for clues to crbug/95101 if (!cache->get()->GetEntry(group_record_.manifest_url)) { AppCacheHistograms::AddMissingManifestDetectedAtCallsite( AppCacheHistograms::CALLSITE_1); } } else { (*group) = new AppCacheGroup( storage_, group_record_.manifest_url, group_record_.group_id); group->get()->set_creation_time(group_record_.creation_time); group->get()->AddCache(cache->get()); // TODO(michaeln): histogram is fishing for clues to crbug/95101 if (!cache->get()->GetEntry(group_record_.manifest_url)) { AppCacheHistograms::AddMissingManifestDetectedAtCallsite( AppCacheHistograms::CALLSITE_2); } } DCHECK(group->get()->newest_complete_cache() == cache->get()); // We have to update foriegn entries if MarkEntryAsForeignTasks // are in flight. std::vector urls; storage_->GetPendingForeignMarkingsForCache(cache->get()->cache_id(), &urls); for (std::vector::iterator iter = urls.begin(); iter != urls.end(); ++iter) { DCHECK(cache->get()->GetEntry(*iter)); cache->get()->GetEntry(*iter)->add_types(AppCacheEntry::FOREIGN); } storage_->NotifyStorageAccessed(group_record_.origin); // 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) {} // DatabaseTask: virtual void Run() OVERRIDE; virtual void RunCompleted() OVERRIDE; protected: virtual ~CacheLoadTask() {} private: 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 cache; scoped_refptr group; if (success_ && !storage_->is_disabled()) { DCHECK(cache_record_.cache_id == 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) {} // DatabaseTask: virtual void Run() OVERRIDE; virtual void RunCompleted() OVERRIDE; protected: virtual ~GroupLoadTask() {} private: 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 group; scoped_refptr cache; if (!storage_->is_disabled()) { if (success_) { DCHECK(group_record_.manifest_url == manifest_url_); CreateCacheAndGroupFromRecords(&cache, &group); } else { group = storage_->working_set_.GetGroup(manifest_url_); if (!group) { group = new AppCacheGroup( storage_, 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); void GetQuotaThenSchedule(); void OnQuotaCallback( quota::QuotaStatusCode status, int64 usage, int64 quota); // DatabaseTask: virtual void Run() OVERRIDE; virtual void RunCompleted() OVERRIDE; virtual void CancelCompletion() OVERRIDE; protected: virtual ~StoreGroupAndCacheTask() {} private: scoped_refptr group_; scoped_refptr cache_; bool success_; bool would_exceed_quota_; int64 space_available_; int64 new_origin_usage_; std::vector 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), space_available_(-1), new_origin_usage_(-1) { 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_, &intercept_namespace_records_, &fallback_namespace_records_, &online_whitelist_records_); } void AppCacheStorageImpl::StoreGroupAndCacheTask::GetQuotaThenSchedule() { quota::QuotaManager* quota_manager = NULL; if (storage_->service()->quota_manager_proxy()) { quota_manager = storage_->service()->quota_manager_proxy()->quota_manager(); } if (!quota_manager) { if (storage_->service()->special_storage_policy() && storage_->service()->special_storage_policy()->IsStorageUnlimited( group_record_.origin)) space_available_ = kint64max; Schedule(); return; } // We have to ask the quota manager for the value. storage_->pending_quota_queries_.insert(this); quota_manager->GetUsageAndQuota( group_record_.origin, quota::kStorageTypeTemporary, base::Bind(&StoreGroupAndCacheTask::OnQuotaCallback, this)); } void AppCacheStorageImpl::StoreGroupAndCacheTask::OnQuotaCallback( quota::QuotaStatusCode status, int64 usage, int64 quota) { if (storage_) { if (status == quota::kQuotaStatusOk) space_available_ = std::max(static_cast(0), quota - usage); else space_available_ = 0; storage_->pending_quota_queries_.erase(this); Schedule(); } } void AppCacheStorageImpl::StoreGroupAndCacheTask::Run() { DCHECK(!success_); sql::Connection* connection = database_->db_connection(); if (!connection) return; sql::Transaction transaction(connection); if (!transaction.Begin()) return; int64 old_origin_usage = database_->GetOriginUsage(group_record_.origin); 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 existing_response_ids; database_->FindResponseIdsForCacheAsSet(cache.cache_id, &existing_response_ids); // Remove those that remain in the new cache. std::vector::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::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_->DeleteNamespacesForCache(cache.cache_id) && database_->DeleteOnlineWhiteListForCache(cache.cache_id) && database_->InsertDeletableResponseIds(newly_deletable_response_ids_); // TODO(michaeln): store group_id too with deletable ids } else { NOTREACHED() << "A existing group without a cache is unexpected"; } } success_ = success_ && database_->InsertCache(&cache_record_) && database_->InsertEntryRecords(entry_records_) && database_->InsertNamespaceRecords(intercept_namespace_records_) && database_->InsertNamespaceRecords(fallback_namespace_records_) && database_->InsertOnlineWhiteListRecords(online_whitelist_records_); if (!success_) return; new_origin_usage_ = database_->GetOriginUsage(group_record_.origin); // Only check quota when the new usage exceeds the old usage. if (new_origin_usage_ <= old_origin_usage) { success_ = transaction.Commit(); return; } // Use a simple hard-coded value when not using quota management. if (space_available_ == -1) { if (new_origin_usage_ > kDefaultQuota) { would_exceed_quota_ = true; success_ = false; return; } success_ = transaction.Commit(); return; } // Check limits based on the space availbable given to us via the // quota system. int64 delta = new_origin_usage_ - old_origin_usage; if (delta > space_available_) { would_exceed_quota_ = true; success_ = false; return; } success_ = transaction.Commit(); } void AppCacheStorageImpl::StoreGroupAndCacheTask::RunCompleted() { if (success_) { storage_->UpdateUsageMapAndNotify( group_->manifest_url().GetOrigin(), new_origin_usage_); if (cache_ != group_->newest_complete_cache()) { cache_->set_complete(true); group_->AddCache(cache_); } if (group_->creation_time().is_null()) group_->set_creation_time(group_record_.creation_time); group_->AddNewlyDeletableResponseIds(&newly_deletable_response_ids_); } FOR_EACH_DELEGATE(delegates_, OnGroupAndNewestCacheStored(group_, cache_, success_, would_exceed_quota_)); group_ = NULL; cache_ = NULL; // TODO(michaeln): if (would_exceed_quota_) what if the current usage // also exceeds the quota? http://crbug.com/83968 } 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 ------- // Helpers for FindMainResponseTask::Run() namespace { class SortByCachePreference : public std::binary_function< AppCacheDatabase::EntryRecord, AppCacheDatabase::EntryRecord, bool> { public: SortByCachePreference(int64 preferred_id, const std::set& in_use_ids) : preferred_id_(preferred_id), in_use_ids_(in_use_ids) { } bool operator()( const AppCacheDatabase::EntryRecord& lhs, const AppCacheDatabase::EntryRecord& rhs) { return compute_value(lhs) > compute_value(rhs); } private: int compute_value(const AppCacheDatabase::EntryRecord& entry) { if (entry.cache_id == preferred_id_) return 100; else if (in_use_ids_.find(entry.cache_id) != in_use_ids_.end()) return 50; return 0; } int64 preferred_id_; const std::set& in_use_ids_; }; bool SortByLength( const AppCacheDatabase::NamespaceRecord& lhs, const AppCacheDatabase::NamespaceRecord& rhs) { return lhs.namespace_url.spec().length() > rhs.namespace_url.spec().length(); } class NetworkNamespaceHelper { public: explicit NetworkNamespaceHelper(AppCacheDatabase* database) : database_(database) { } bool IsInNetworkNamespace(const GURL& url, int64 cache_id) { const std::vector kEmptyVector; typedef std::pair InsertResult; InsertResult result = namespaces_map_.insert( WhiteListMap::value_type(cache_id, kEmptyVector)); if (result.second) GetOnlineWhiteListForCache(cache_id, &result.first->second); return AppCache::IsInNetworkNamespace(url, result.first->second); } private: void GetOnlineWhiteListForCache( int64 cache_id, std::vector* urls) { DCHECK(urls && urls->empty()); typedef std::vector WhiteListVector; WhiteListVector records; if (!database_->FindOnlineWhiteListForCache(cache_id, &records)) return; WhiteListVector::const_iterator iter = records.begin(); while (iter != records.end()) { urls->push_back(iter->namespace_url); ++iter; } } // Key is cache id typedef std::map > WhiteListMap; WhiteListMap namespaces_map_; AppCacheDatabase* database_; }; } // namespace class AppCacheStorageImpl::FindMainResponseTask : public DatabaseTask { public: FindMainResponseTask(AppCacheStorageImpl* storage, const GURL& url, const GURL& preferred_manifest_url, const AppCacheWorkingSet::GroupMap* groups_in_use) : DatabaseTask(storage), url_(url), preferred_manifest_url_(preferred_manifest_url), cache_id_(kNoCacheId), group_id_(0) { 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()); } } } // DatabaseTask: virtual void Run() OVERRIDE; virtual void RunCompleted() OVERRIDE; protected: virtual ~FindMainResponseTask() {} private: typedef std::vector NamespaceRecordPtrVector; bool FindExactMatch(int64 preferred_id); bool FindNamespaceMatch(int64 preferred_id); bool FindNamespaceHelper( int64 preferred_cache_id, AppCacheDatabase::NamespaceRecordVector* namespaces, NetworkNamespaceHelper* network_namespace_helper); bool FindFirstValidNamespace(const NamespaceRecordPtrVector& namespaces); GURL url_; GURL preferred_manifest_url_; std::set cache_ids_in_use_; AppCacheEntry entry_; AppCacheEntry fallback_entry_; GURL namespace_entry_url_; int64 cache_id_; int64 group_id_; GURL manifest_url_; }; void AppCacheStorageImpl::FindMainResponseTask::Run() { // NOTE: The heuristics around choosing amoungst multiple candidates // is underspecified, and just plain not fully understood. This needs // to be refined. // The 'preferred_manifest_url' is the url of the manifest associated // with the page that opened or embedded the page being loaded now. // We have a strong preference to use resources from that cache. // We also have a lesser bias to use resources from caches that are currently // being used by other unrelated pages. // TODO(michaeln): come up with a 'preferred_manifest_url' in more cases // - when navigating a frame whose current contents are from an appcache // - when clicking an href in a frame that is appcached int64 preferred_cache_id = kNoCacheId; if (!preferred_manifest_url_.is_empty()) { AppCacheDatabase::GroupRecord preferred_group; AppCacheDatabase::CacheRecord preferred_cache; if (database_->FindGroupForManifestUrl( preferred_manifest_url_, &preferred_group) && database_->FindCacheForGroup( preferred_group.group_id, &preferred_cache)) { preferred_cache_id = preferred_cache.cache_id; } } if (FindExactMatch(preferred_cache_id) || FindNamespaceMatch(preferred_cache_id)) { // We found something. DCHECK(cache_id_ != kNoCacheId && !manifest_url_.is_empty() && group_id_ != 0); return; } // We didn't find anything. DCHECK(cache_id_ == kNoCacheId && manifest_url_.is_empty() && group_id_ == 0); } bool AppCacheStorageImpl:: FindMainResponseTask::FindExactMatch(int64 preferred_cache_id) { std::vector entries; if (database_->FindEntriesForUrl(url_, &entries) && !entries.empty()) { // Sort them in order of preference, from the preferred_cache first, // followed by hits from caches that are 'in use', then the rest. std::sort(entries.begin(), entries.end(), SortByCachePreference(preferred_cache_id, cache_ids_in_use_)); // Take the first with a valid, non-foreign entry. std::vector::iterator iter; for (iter = entries.begin(); iter < entries.end(); ++iter) { AppCacheDatabase::GroupRecord group_record; if ((iter->flags & AppCacheEntry::FOREIGN) || !database_->FindGroupForCache(iter->cache_id, &group_record)) { continue; } manifest_url_ = group_record.manifest_url; group_id_ = group_record.group_id; entry_ = AppCacheEntry(iter->flags, iter->response_id); cache_id_ = iter->cache_id; return true; // We found an exact match. } } return false; } bool AppCacheStorageImpl:: FindMainResponseTask::FindNamespaceMatch(int64 preferred_cache_id) { AppCacheDatabase::NamespaceRecordVector all_intercepts; AppCacheDatabase::NamespaceRecordVector all_fallbacks; if (!database_->FindNamespacesForOrigin( url_.GetOrigin(), &all_intercepts, &all_fallbacks) || (all_intercepts.empty() && all_fallbacks.empty())) { return false; } NetworkNamespaceHelper network_namespace_helper(database_); if (FindNamespaceHelper(preferred_cache_id, &all_intercepts, &network_namespace_helper) || FindNamespaceHelper(preferred_cache_id, &all_fallbacks, &network_namespace_helper)) { return true; } return false; } bool AppCacheStorageImpl:: FindMainResponseTask::FindNamespaceHelper( int64 preferred_cache_id, AppCacheDatabase::NamespaceRecordVector* namespaces, NetworkNamespaceHelper* network_namespace_helper) { // Sort them by length, longer matches within the same cache/bucket take // precedence. std::sort(namespaces->begin(), namespaces->end(), SortByLength); NamespaceRecordPtrVector preferred_namespaces; NamespaceRecordPtrVector inuse_namespaces; NamespaceRecordPtrVector other_namespaces; std::vector::iterator iter; for (iter = namespaces->begin(); iter < namespaces->end(); ++iter) { // Skip those that aren't a prefix match. if (!StartsWithASCII(url_.spec(), iter->namespace_url.spec(), true)) continue; // Skip namespaces where the requested url falls into a network // namespace of its containing appcache. if (network_namespace_helper->IsInNetworkNamespace(url_, iter->cache_id)) continue; // Bin them into one of our three buckets. if (iter->cache_id == preferred_cache_id) preferred_namespaces.push_back(&(*iter)); else if (cache_ids_in_use_.find(iter->cache_id) != cache_ids_in_use_.end()) inuse_namespaces.push_back(&(*iter)); else other_namespaces.push_back(&(*iter)); } if (FindFirstValidNamespace(preferred_namespaces) || FindFirstValidNamespace(inuse_namespaces) || FindFirstValidNamespace(other_namespaces)) return true; // We found one. // We didn't find anything. return false; } bool AppCacheStorageImpl:: FindMainResponseTask::FindFirstValidNamespace( const NamespaceRecordPtrVector& namespaces) { // Take the first with a valid, non-foreign entry. NamespaceRecordPtrVector::const_iterator iter; for (iter = namespaces.begin(); iter < namespaces.end(); ++iter) { AppCacheDatabase::EntryRecord entry_record; if (database_->FindEntry((*iter)->cache_id, (*iter)->target_url, &entry_record)) { AppCacheDatabase::GroupRecord group_record; if ((entry_record.flags & AppCacheEntry::FOREIGN) || !database_->FindGroupForCache(entry_record.cache_id, &group_record)) { continue; } manifest_url_ = group_record.manifest_url; group_id_ = group_record.group_id; cache_id_ = (*iter)->cache_id; namespace_entry_url_ = (*iter)->target_url; if ((*iter)->type == FALLBACK_NAMESPACE) fallback_entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id); else entry_ = AppCacheEntry(entry_record.flags, entry_record.response_id); return true; // We found one. } } return false; // We didn't find a match. } void AppCacheStorageImpl::FindMainResponseTask::RunCompleted() { storage_->CallOnMainResponseFound( &delegates_, url_, entry_, namespace_entry_url_, fallback_entry_, cache_id_, group_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) {} // DatabaseTask: virtual void Run() OVERRIDE; virtual void RunCompleted() OVERRIDE; protected: virtual ~MarkEntryAsForeignTask() {} private: 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); // DatabaseTask: virtual void Run() OVERRIDE; virtual void RunCompleted() OVERRIDE; virtual void CancelCompletion() OVERRIDE; protected: virtual ~MakeGroupObsoleteTask() {} private: scoped_refptr group_; int64 group_id_; GURL origin_; bool success_; int64 new_origin_usage_; std::vector newly_deletable_response_ids_; }; AppCacheStorageImpl::MakeGroupObsoleteTask::MakeGroupObsoleteTask( AppCacheStorageImpl* storage, AppCacheGroup* group) : DatabaseTask(storage), group_(group), group_id_(group->group_id()), origin_(group->manifest_url().GetOrigin()), success_(false), new_origin_usage_(-1) { } 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. new_origin_usage_ = database_->GetOriginUsage(origin_); success_ = true; return; } DCHECK_EQ(group_record.origin, origin_); success_ = DeleteGroupAndRelatedRecords(database_, group_id_, &newly_deletable_response_ids_); new_origin_usage_ = database_->GetOriginUsage(origin_); success_ = success_ && transaction.Commit(); } void AppCacheStorageImpl::MakeGroupObsoleteTask::RunCompleted() { if (success_) { group_->set_obsolete(true); if (!storage_->is_disabled()) { storage_->UpdateUsageMapAndNotify(origin_, new_origin_usage_); 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) {} // DatabaseTask: virtual void Run() OVERRIDE; virtual void RunCompleted() OVERRIDE; protected: virtual ~GetDeletableResponseIdsTask() {} private: int64 max_rowid_; std::vector response_ids_; }; void AppCacheStorageImpl::GetDeletableResponseIdsTask::Run() { const int kSqlLimit = 1000; database_->GetDeletableResponseIds(&response_ids_, max_rowid_, kSqlLimit); // TODO(michaeln): retrieve group_ids too } 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) {} // DatabaseTask: virtual void Run() OVERRIDE; std::vector response_ids_; protected: virtual ~InsertDeletableResponseIdsTask() {} }; void AppCacheStorageImpl::InsertDeletableResponseIdsTask::Run() { database_->InsertDeletableResponseIds(response_ids_); // TODO(michaeln): store group_ids too } // DeleteDeletableResponseIdsTask ------- class AppCacheStorageImpl::DeleteDeletableResponseIdsTask : public DatabaseTask { public: explicit DeleteDeletableResponseIdsTask(AppCacheStorageImpl* storage) : DatabaseTask(storage) {} // DatabaseTask: virtual void Run() OVERRIDE; std::vector response_ids_; protected: virtual ~DeleteDeletableResponseIdsTask() {} }; void AppCacheStorageImpl::DeleteDeletableResponseIdsTask::Run() { database_->DeleteDeletableResponseIds(response_ids_); } // UpdateGroupLastAccessTimeTask ------- class AppCacheStorageImpl::UpdateGroupLastAccessTimeTask : public DatabaseTask { public: UpdateGroupLastAccessTimeTask( AppCacheStorageImpl* storage, AppCacheGroup* group, base::Time time) : DatabaseTask(storage), group_id_(group->group_id()), last_access_time_(time) { storage->NotifyStorageAccessed(group->manifest_url().GetOrigin()); } // DatabaseTask: virtual void Run() OVERRIDE; protected: virtual ~UpdateGroupLastAccessTimeTask() {} private: 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), database_(NULL), is_disabled_(false), ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) { } AppCacheStorageImpl::~AppCacheStorageImpl() { std::for_each(pending_quota_queries_.begin(), pending_quota_queries_.end(), std::mem_fun(&DatabaseTask::CancelCompletion)); std::for_each(scheduled_database_tasks_.begin(), scheduled_database_tasks_.end(), std::mem_fun(&DatabaseTask::CancelCompletion)); if (database_ && !db_thread_->PostTask( FROM_HERE, base::Bind(&ClearSessionOnlyOrigins, database_, make_scoped_refptr(service_->special_storage_policy()), service()->force_keep_session_state()))) { delete database_; } } void AppCacheStorageImpl::Initialize(const base::FilePath& cache_directory, base::MessageLoopProxy* db_thread, base::MessageLoopProxy* cache_thread) { DCHECK(db_thread); cache_directory_ = cache_directory; is_incognito_ = cache_directory_.empty(); base::FilePath db_file_path; if (!is_incognito_) db_file_path = cache_directory_.Append(kAppCacheDatabaseName); database_ = new AppCacheDatabase(db_file_path); db_thread_ = db_thread; cache_thread_ = cache_thread; scoped_refptr task(new InitTask(this)); task->Schedule(); } void AppCacheStorageImpl::Disable() { if (is_disabled_) return; VLOG(1) << "Disabling appcache storage."; is_disabled_ = true; ClearUsageMapAndNotify(); working_set()->Disable(); if (disk_cache_.get()) disk_cache_->Disable(); scoped_refptr task(new DisableDatabaseTask(this)); task->Schedule(); } void AppCacheStorageImpl::GetAllInfo(Delegate* delegate) { DCHECK(delegate); scoped_refptr 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 update_task( new UpdateGroupLastAccessTimeTask( this, cache->owning_group(), base::Time::Now())); update_task->Schedule(); } return; } scoped_refptr 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 update_task( new UpdateGroupLastAccessTimeTask( this, group, base::Time::Now())); update_task->Schedule(); return; } scoped_refptr task(GetPendingGroupLoadTask(manifest_url)); if (task) { task->AddDelegate(GetOrCreateDelegateReference(delegate)); return; } if (usage_map_.find(manifest_url.GetOrigin()) == usage_map_.end()) { // No need to query the database, return a new group immediately. scoped_refptr group(new AppCacheGroup( service_->storage(), 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 task( new StoreGroupAndCacheTask(this, group, newest_cache)); task->AddDelegate(GetOrCreateDelegateReference(delegate)); task->GetQuotaThenSchedule(); // TODO(michaeln): histogram is fishing for clues to crbug/95101 if (!newest_cache->GetEntry(group->manifest_url())) { AppCacheHistograms::AddMissingManifestDetectedAtCallsite( AppCacheHistograms::CALLSITE_3); } } void AppCacheStorageImpl::FindResponseForMainRequest( const GURL& url, const GURL& preferred_manifest_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) { if (!preferred_manifest_url.is_empty()) { AppCacheWorkingSet::GroupMap::const_iterator found = groups_in_use->find(preferred_manifest_url); if (found != groups_in_use->end() && FindResponseForMainRequestInGroup( found->second, *url_ptr, delegate)) { return; } } else { for (AppCacheWorkingSet::GroupMap::const_iterator it = groups_in_use->begin(); it != groups_in_use->end(); ++it) { if (FindResponseForMainRequestInGroup( it->second, *url_ptr, delegate)) { return; } } } } if (IsInitTaskComplete() && usage_map_.find(origin) == usage_map_.end()) { // No need to query the database, return async'ly but without going thru // the DB thread. scoped_refptr no_group; scoped_refptr no_cache; ScheduleSimpleTask( base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse, weak_factory_.GetWeakPtr(), 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 task( new FindMainResponseTask(this, *url_ptr, preferred_manifest_url, groups_in_use)); task->AddDelegate(GetOrCreateDelegateReference(delegate)); task->Schedule(); } bool AppCacheStorageImpl::FindResponseForMainRequestInGroup( AppCacheGroup* group, const GURL& url, Delegate* delegate) { AppCache* cache = group->newest_complete_cache(); if (group->is_obsolete() || !cache) return false; AppCacheEntry* entry = cache->GetEntry(url); if (!entry || entry->IsForeign()) return false; ScheduleSimpleTask( base::Bind(&AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse, weak_factory_.GetWeakPtr(), url, *entry, make_scoped_refptr(group), make_scoped_refptr(cache), make_scoped_refptr(GetOrCreateDelegateReference(delegate)))); return true; } void AppCacheStorageImpl::DeliverShortCircuitedFindMainResponse( const GURL& url, const AppCacheEntry& found_entry, scoped_refptr group, scoped_refptr cache, scoped_refptr delegate_ref) { if (delegate_ref->delegate) { DelegateReferenceVector delegates(1, delegate_ref); CallOnMainResponseFound( &delegates, url, found_entry, GURL(), AppCacheEntry(), cache.get() ? cache->cache_id() : kNoCacheId, group.get() ? group->group_id() : kNoCacheId, group.get() ? group->manifest_url() : GURL()); } } void AppCacheStorageImpl::CallOnMainResponseFound( DelegateReferenceVector* delegates, const GURL& url, const AppCacheEntry& entry, const GURL& namespace_entry_url, const AppCacheEntry& fallback_entry, int64 cache_id, int64 group_id, const GURL& manifest_url) { FOR_EACH_DELEGATE( (*delegates), OnMainResponseFound(url, entry, namespace_entry_url, fallback_entry, cache_id, group_id, manifest_url)); } 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; GURL intercept_namespace_not_used; cache->FindResponseForRequest( url, found_entry, &intercept_namespace_not_used, 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 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 task( new MakeGroupObsoleteTask(this, group)); task->AddDelegate(GetOrCreateDelegateReference(delegate)); task->Schedule(); } AppCacheResponseReader* AppCacheStorageImpl::CreateResponseReader( const GURL& manifest_url, int64 group_id, int64 response_id) { return new AppCacheResponseReader(response_id, group_id, disk_cache()); } AppCacheResponseWriter* AppCacheStorageImpl::CreateResponseWriter( const GURL& manifest_url, int64 group_id) { return new AppCacheResponseWriter(NewResponseId(), group_id, disk_cache()); } void AppCacheStorageImpl::DoomResponses( const GURL& manifest_url, const std::vector& 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 task( new InsertDeletableResponseIdsTask(this)); task->response_ids_ = response_ids; task->Schedule(); } void AppCacheStorageImpl::DeleteResponses( const GURL& manifest_url, const std::vector& response_ids) { if (response_ids.empty()) return; StartDeletingResponses(response_ids); } void AppCacheStorageImpl::PurgeMemory() { scoped_refptr task(new CloseConnectionTask(this)); task->Schedule(); } void AppCacheStorageImpl::DelayedStartDeletingUnusedResponses() { // Only if we haven't already begun. if (!did_start_deleting_responses_) { scoped_refptr task( new GetDeletableResponseIdsTask(this, last_deletable_response_rowid_)); task->Schedule(); } } void AppCacheStorageImpl::StartDeletingResponses( const std::vector& 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 base::TimeDelta kDelay = base::TimeDelta::FromMilliseconds(10); MessageLoop::current()->PostDelayedTask( FROM_HERE, base::Bind(&AppCacheStorageImpl::DeleteOneResponse, weak_factory_.GetWeakPtr()), kDelay); 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; } // TODO(michaeln): add group_id to DoomEntry args int64 id = deletable_response_ids_.front(); int rv = disk_cache_->DoomEntry( id, base::Bind(&AppCacheStorageImpl::OnDeletedOneResponse, base::Unretained(this))); 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 task( new DeleteDeletableResponseIdsTask(this)); task->response_ids_.swap(deleted_response_ids_); task->Schedule(); } if (deletable_response_ids_.empty()) { scoped_refptr 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* 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(const base::Closure& task) { pending_simple_tasks_.push_back(task); MessageLoop::current()->PostTask( FROM_HERE, base::Bind(&AppCacheStorageImpl::RunOnePendingSimpleTask, weak_factory_.GetWeakPtr())); } void AppCacheStorageImpl::RunOnePendingSimpleTask() { DCHECK(!pending_simple_tasks_.empty()); base::Closure task = pending_simple_tasks_.front(); pending_simple_tasks_.pop_front(); task.Run(); } 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, base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized, base::Unretained(this))); } else { rv = disk_cache_->InitWithDiskBackend( cache_directory_.Append(kDiskCacheDirectoryName), kMaxDiskCacheSize, false, cache_thread_, base::Bind(&AppCacheStorageImpl::OnDiskCacheInitialized, base::Unretained(this))); } // 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_) { VLOG(1) << "Deleting existing appcache data and starting over."; db_thread_->PostTask( FROM_HERE, base::Bind(base::IgnoreResult(&file_util::Delete), cache_directory_, true)); } } } } // namespace appcache