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