indexed_db_context_impl.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/browser/indexed_db/indexed_db_context_impl.h" 6 7#include <vector> 8 9#include "base/bind.h" 10#include "base/command_line.h" 11#include "base/file_util.h" 12#include "base/files/file_enumerator.h" 13#include "base/logging.h" 14#include "base/sequenced_task_runner.h" 15#include "base/strings/string_util.h" 16#include "base/strings/utf_string_conversions.h" 17#include "base/threading/thread_restrictions.h" 18#include "content/browser/browser_main_loop.h" 19#include "content/browser/indexed_db/indexed_db_connection.h" 20#include "content/browser/indexed_db/indexed_db_factory.h" 21#include "content/browser/indexed_db/indexed_db_quota_client.h" 22#include "content/public/browser/browser_thread.h" 23#include "content/public/browser/indexed_db_info.h" 24#include "content/public/common/content_switches.h" 25#include "webkit/browser/database/database_util.h" 26#include "webkit/browser/quota/quota_manager.h" 27#include "webkit/browser/quota/special_storage_policy.h" 28#include "webkit/common/database/database_identifier.h" 29 30using webkit_database::DatabaseUtil; 31 32namespace content { 33const base::FilePath::CharType IndexedDBContextImpl::kIndexedDBDirectory[] = 34 FILE_PATH_LITERAL("IndexedDB"); 35 36static const base::FilePath::CharType kIndexedDBExtension[] = 37 FILE_PATH_LITERAL(".indexeddb"); 38 39static const base::FilePath::CharType kLevelDBExtension[] = 40 FILE_PATH_LITERAL(".leveldb"); 41 42namespace { 43 44// This may be called after the IndexedDBContext is destroyed. 45void GetAllOriginsAndPaths(const base::FilePath& indexeddb_path, 46 std::vector<GURL>* origins, 47 std::vector<base::FilePath>* file_paths) { 48 // TODO(jsbell): DCHECK that this is running on an IndexedDB thread, 49 // if a global handle to it is ever available. 50 if (indexeddb_path.empty()) 51 return; 52 base::FileEnumerator file_enumerator( 53 indexeddb_path, false, base::FileEnumerator::DIRECTORIES); 54 for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty(); 55 file_path = file_enumerator.Next()) { 56 if (file_path.Extension() == kLevelDBExtension && 57 file_path.RemoveExtension().Extension() == kIndexedDBExtension) { 58 std::string origin_id = file_path.BaseName().RemoveExtension() 59 .RemoveExtension().MaybeAsASCII(); 60 origins->push_back(webkit_database::GetOriginFromIdentifier(origin_id)); 61 if (file_paths) 62 file_paths->push_back(file_path); 63 } 64 } 65} 66 67// This will be called after the IndexedDBContext is destroyed. 68void ClearSessionOnlyOrigins( 69 const base::FilePath& indexeddb_path, 70 scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy) { 71 // TODO(jsbell): DCHECK that this is running on an IndexedDB thread, 72 // if a global handle to it is ever available. 73 std::vector<GURL> origins; 74 std::vector<base::FilePath> file_paths; 75 GetAllOriginsAndPaths(indexeddb_path, &origins, &file_paths); 76 DCHECK_EQ(origins.size(), file_paths.size()); 77 std::vector<base::FilePath>::const_iterator file_path_iter = 78 file_paths.begin(); 79 for (std::vector<GURL>::const_iterator iter = origins.begin(); 80 iter != origins.end(); 81 ++iter, ++file_path_iter) { 82 if (!special_storage_policy->IsStorageSessionOnly(*iter)) 83 continue; 84 if (special_storage_policy->IsStorageProtected(*iter)) 85 continue; 86 base::Delete(*file_path_iter, true); 87 } 88} 89 90} // namespace 91 92IndexedDBContextImpl::IndexedDBContextImpl( 93 const base::FilePath& data_path, 94 quota::SpecialStoragePolicy* special_storage_policy, 95 quota::QuotaManagerProxy* quota_manager_proxy, 96 base::SequencedTaskRunner* task_runner) 97 : force_keep_session_state_(false), 98 special_storage_policy_(special_storage_policy), 99 quota_manager_proxy_(quota_manager_proxy), 100 task_runner_(task_runner) { 101 if (!data_path.empty()) 102 data_path_ = data_path.Append(kIndexedDBDirectory); 103 if (quota_manager_proxy && 104 !CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) { 105 quota_manager_proxy->RegisterClient(new IndexedDBQuotaClient(this)); 106 } 107} 108 109IndexedDBFactory* IndexedDBContextImpl::GetIDBFactory() { 110 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 111 if (!idb_factory_) { 112 // Prime our cache of origins with existing databases so we can 113 // detect when dbs are newly created. 114 GetOriginSet(); 115 idb_factory_ = IndexedDBFactory::Create(); 116 } 117 return idb_factory_; 118} 119 120std::vector<GURL> IndexedDBContextImpl::GetAllOrigins() { 121 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 122 std::vector<GURL> origins; 123 std::set<GURL>* origins_set = GetOriginSet(); 124 for (std::set<GURL>::const_iterator iter = origins_set->begin(); 125 iter != origins_set->end(); 126 ++iter) { 127 origins.push_back(*iter); 128 } 129 return origins; 130} 131 132std::vector<IndexedDBInfo> IndexedDBContextImpl::GetAllOriginsInfo() { 133 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 134 std::vector<GURL> origins = GetAllOrigins(); 135 std::vector<IndexedDBInfo> result; 136 for (std::vector<GURL>::const_iterator iter = origins.begin(); 137 iter != origins.end(); 138 ++iter) { 139 const GURL& origin_url = *iter; 140 141 base::FilePath idb_directory = GetFilePath(origin_url); 142 result.push_back(IndexedDBInfo(origin_url, 143 GetOriginDiskUsage(origin_url), 144 GetOriginLastModified(origin_url), 145 idb_directory)); 146 } 147 return result; 148} 149 150int64 IndexedDBContextImpl::GetOriginDiskUsage(const GURL& origin_url) { 151 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 152 if (data_path_.empty() || !IsInOriginSet(origin_url)) 153 return 0; 154 EnsureDiskUsageCacheInitialized(origin_url); 155 return origin_size_map_[origin_url]; 156} 157 158base::Time IndexedDBContextImpl::GetOriginLastModified(const GURL& origin_url) { 159 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 160 if (data_path_.empty() || !IsInOriginSet(origin_url)) 161 return base::Time(); 162 base::FilePath idb_directory = GetFilePath(origin_url); 163 base::PlatformFileInfo file_info; 164 if (!file_util::GetFileInfo(idb_directory, &file_info)) 165 return base::Time(); 166 return file_info.last_modified; 167} 168 169void IndexedDBContextImpl::DeleteForOrigin(const GURL& origin_url) { 170 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 171 ForceClose(origin_url); 172 if (data_path_.empty() || !IsInOriginSet(origin_url)) 173 return; 174 175 base::FilePath idb_directory = GetFilePath(origin_url); 176 EnsureDiskUsageCacheInitialized(origin_url); 177 const bool recursive = true; 178 bool deleted = base::Delete(idb_directory, recursive); 179 180 QueryDiskAndUpdateQuotaUsage(origin_url); 181 if (deleted) { 182 RemoveFromOriginSet(origin_url); 183 origin_size_map_.erase(origin_url); 184 space_available_map_.erase(origin_url); 185 } 186} 187 188void IndexedDBContextImpl::ForceClose(const GURL& origin_url) { 189 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 190 if (data_path_.empty() || !IsInOriginSet(origin_url)) 191 return; 192 193 if (connections_.find(origin_url) != connections_.end()) { 194 ConnectionSet& connections = connections_[origin_url]; 195 ConnectionSet::iterator it = connections.begin(); 196 while (it != connections.end()) { 197 // Remove before closing so callbacks don't double-erase 198 IndexedDBConnection* connection = *it; 199 connections.erase(it++); 200 connection->ForceClose(); 201 } 202 DCHECK_EQ(connections_[origin_url].size(), 0UL); 203 connections_.erase(origin_url); 204 } 205} 206 207base::FilePath IndexedDBContextImpl::GetFilePath(const GURL& origin_url) { 208 std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url); 209 return GetIndexedDBFilePath(origin_id); 210} 211 212base::FilePath IndexedDBContextImpl::GetFilePathForTesting( 213 const std::string& origin_id) const { 214 return GetIndexedDBFilePath(origin_id); 215} 216 217void IndexedDBContextImpl::SetTaskRunnerForTesting( 218 base::SequencedTaskRunner* task_runner) { 219 DCHECK(!task_runner_); 220 task_runner_ = task_runner; 221} 222 223void IndexedDBContextImpl::ConnectionOpened(const GURL& origin_url, 224 IndexedDBConnection* connection) { 225 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 226 DCHECK_EQ(connections_[origin_url].count(connection), 0UL); 227 if (quota_manager_proxy()) { 228 quota_manager_proxy()->NotifyStorageAccessed( 229 quota::QuotaClient::kIndexedDatabase, 230 origin_url, 231 quota::kStorageTypeTemporary); 232 } 233 connections_[origin_url].insert(connection); 234 if (AddToOriginSet(origin_url)) { 235 // A newly created db, notify the quota system. 236 QueryDiskAndUpdateQuotaUsage(origin_url); 237 } else { 238 EnsureDiskUsageCacheInitialized(origin_url); 239 } 240 QueryAvailableQuota(origin_url); 241} 242 243void IndexedDBContextImpl::ConnectionClosed(const GURL& origin_url, 244 IndexedDBConnection* connection) { 245 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 246 // May not be in the map if connection was forced to close 247 if (connections_.find(origin_url) == connections_.end() || 248 connections_[origin_url].count(connection) != 1) 249 return; 250 if (quota_manager_proxy()) { 251 quota_manager_proxy()->NotifyStorageAccessed( 252 quota::QuotaClient::kIndexedDatabase, 253 origin_url, 254 quota::kStorageTypeTemporary); 255 } 256 connections_[origin_url].erase(connection); 257 if (connections_[origin_url].size() == 0) { 258 QueryDiskAndUpdateQuotaUsage(origin_url); 259 connections_.erase(origin_url); 260 } 261} 262 263void IndexedDBContextImpl::TransactionComplete(const GURL& origin_url) { 264 DCHECK(connections_.find(origin_url) != connections_.end() && 265 connections_[origin_url].size() > 0); 266 QueryDiskAndUpdateQuotaUsage(origin_url); 267 QueryAvailableQuota(origin_url); 268} 269 270bool IndexedDBContextImpl::WouldBeOverQuota(const GURL& origin_url, 271 int64 additional_bytes) { 272 if (space_available_map_.find(origin_url) == space_available_map_.end()) { 273 // We haven't heard back from the QuotaManager yet, just let it through. 274 return false; 275 } 276 bool over_quota = additional_bytes > space_available_map_[origin_url]; 277 return over_quota; 278} 279 280bool IndexedDBContextImpl::IsOverQuota(const GURL& origin_url) { 281 const int kOneAdditionalByte = 1; 282 return WouldBeOverQuota(origin_url, kOneAdditionalByte); 283} 284 285quota::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() { 286 return quota_manager_proxy_; 287} 288 289IndexedDBContextImpl::~IndexedDBContextImpl() { 290 if (idb_factory_) { 291 IndexedDBFactory* factory = idb_factory_; 292 factory->AddRef(); 293 idb_factory_ = NULL; 294 if (!task_runner_->ReleaseSoon(FROM_HERE, factory)) { 295 factory->Release(); 296 } 297 } 298 299 if (data_path_.empty()) 300 return; 301 302 if (force_keep_session_state_) 303 return; 304 305 bool has_session_only_databases = 306 special_storage_policy_ && 307 special_storage_policy_->HasSessionOnlyOrigins(); 308 309 // Clearning only session-only databases, and there are none. 310 if (!has_session_only_databases) 311 return; 312 313 TaskRunner()->PostTask( 314 FROM_HERE, 315 base::Bind( 316 &ClearSessionOnlyOrigins, data_path_, special_storage_policy_)); 317} 318 319base::FilePath IndexedDBContextImpl::GetIndexedDBFilePath( 320 const std::string& origin_id) const { 321 DCHECK(!data_path_.empty()); 322 return data_path_.AppendASCII(origin_id).AddExtension(kIndexedDBExtension) 323 .AddExtension(kLevelDBExtension); 324} 325 326int64 IndexedDBContextImpl::ReadUsageFromDisk(const GURL& origin_url) const { 327 if (data_path_.empty()) 328 return 0; 329 std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url); 330 base::FilePath file_path = GetIndexedDBFilePath(origin_id); 331 return base::ComputeDirectorySize(file_path); 332} 333 334void IndexedDBContextImpl::EnsureDiskUsageCacheInitialized( 335 const GURL& origin_url) { 336 if (origin_size_map_.find(origin_url) == origin_size_map_.end()) 337 origin_size_map_[origin_url] = ReadUsageFromDisk(origin_url); 338} 339 340void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage( 341 const GURL& origin_url) { 342 int64 former_disk_usage = origin_size_map_[origin_url]; 343 int64 current_disk_usage = ReadUsageFromDisk(origin_url); 344 int64 difference = current_disk_usage - former_disk_usage; 345 if (difference) { 346 origin_size_map_[origin_url] = current_disk_usage; 347 // quota_manager_proxy() is NULL in unit tests. 348 if (quota_manager_proxy()) { 349 quota_manager_proxy()->NotifyStorageModified( 350 quota::QuotaClient::kIndexedDatabase, 351 origin_url, 352 quota::kStorageTypeTemporary, 353 difference); 354 } 355 } 356} 357 358void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url, 359 quota::QuotaStatusCode status, 360 int64 usage, 361 int64 quota) { 362 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 363 DCHECK(status == quota::kQuotaStatusOk || status == quota::kQuotaErrorAbort) 364 << "status was " << status; 365 if (status == quota::kQuotaErrorAbort) { 366 // We seem to no longer care to wait around for the answer. 367 return; 368 } 369 TaskRunner()->PostTask(FROM_HERE, 370 base::Bind(&IndexedDBContextImpl::GotUpdatedQuota, 371 this, 372 origin_url, 373 usage, 374 quota)); 375} 376 377void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url, 378 int64 usage, 379 int64 quota) { 380 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 381 space_available_map_[origin_url] = quota - usage; 382} 383 384void IndexedDBContextImpl::QueryAvailableQuota(const GURL& origin_url) { 385 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { 386 DCHECK(TaskRunner()->RunsTasksOnCurrentThread()); 387 if (quota_manager_proxy()) { 388 BrowserThread::PostTask( 389 BrowserThread::IO, 390 FROM_HERE, 391 base::Bind( 392 &IndexedDBContextImpl::QueryAvailableQuota, this, origin_url)); 393 } 394 return; 395 } 396 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 397 if (!quota_manager_proxy() || !quota_manager_proxy()->quota_manager()) 398 return; 399 quota_manager_proxy()->quota_manager()->GetUsageAndQuota( 400 origin_url, 401 quota::kStorageTypeTemporary, 402 base::Bind(&IndexedDBContextImpl::GotUsageAndQuota, this, origin_url)); 403} 404 405std::set<GURL>* IndexedDBContextImpl::GetOriginSet() { 406 if (!origin_set_) { 407 origin_set_.reset(new std::set<GURL>); 408 std::vector<GURL> origins; 409 GetAllOriginsAndPaths(data_path_, &origins, NULL); 410 for (std::vector<GURL>::const_iterator iter = origins.begin(); 411 iter != origins.end(); 412 ++iter) { 413 origin_set_->insert(*iter); 414 } 415 } 416 return origin_set_.get(); 417} 418 419void IndexedDBContextImpl::ResetCaches() { 420 origin_set_.reset(); 421 origin_size_map_.clear(); 422 space_available_map_.clear(); 423} 424 425base::TaskRunner* IndexedDBContextImpl::TaskRunner() const { 426 return task_runner_; 427} 428 429} // namespace content 430