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