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