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 <algorithm>
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 "base/values.h"
19#include "content/browser/browser_main_loop.h"
20#include "content/browser/indexed_db/indexed_db_connection.h"
21#include "content/browser/indexed_db/indexed_db_database.h"
22#include "content/browser/indexed_db/indexed_db_factory.h"
23#include "content/browser/indexed_db/indexed_db_quota_client.h"
24#include "content/browser/indexed_db/indexed_db_transaction.h"
25#include "content/public/browser/browser_thread.h"
26#include "content/public/browser/indexed_db_info.h"
27#include "content/public/common/content_switches.h"
28#include "ui/base/text/bytes_formatting.h"
29#include "webkit/browser/database/database_util.h"
30#include "webkit/browser/quota/quota_manager.h"
31#include "webkit/browser/quota/special_storage_policy.h"
32#include "webkit/common/database/database_identifier.h"
33
34using base::DictionaryValue;
35using base::ListValue;
36using webkit_database::DatabaseUtil;
37
38namespace content {
39const base::FilePath::CharType IndexedDBContextImpl::kIndexedDBDirectory[] =
40    FILE_PATH_LITERAL("IndexedDB");
41
42static const base::FilePath::CharType kIndexedDBExtension[] =
43    FILE_PATH_LITERAL(".indexeddb");
44
45static const base::FilePath::CharType kLevelDBExtension[] =
46    FILE_PATH_LITERAL(".leveldb");
47
48namespace {
49
50// This may be called after the IndexedDBContext is destroyed.
51void GetAllOriginsAndPaths(const base::FilePath& indexeddb_path,
52                           std::vector<GURL>* origins,
53                           std::vector<base::FilePath>* file_paths) {
54  // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
55  // if a global handle to it is ever available.
56  if (indexeddb_path.empty())
57    return;
58  base::FileEnumerator file_enumerator(
59      indexeddb_path, false, base::FileEnumerator::DIRECTORIES);
60  for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty();
61       file_path = file_enumerator.Next()) {
62    if (file_path.Extension() == kLevelDBExtension &&
63        file_path.RemoveExtension().Extension() == kIndexedDBExtension) {
64      std::string origin_id = file_path.BaseName().RemoveExtension()
65          .RemoveExtension().MaybeAsASCII();
66      origins->push_back(webkit_database::GetOriginFromIdentifier(origin_id));
67      if (file_paths)
68        file_paths->push_back(file_path);
69    }
70  }
71}
72
73// This will be called after the IndexedDBContext is destroyed.
74void ClearSessionOnlyOrigins(
75    const base::FilePath& indexeddb_path,
76    scoped_refptr<quota::SpecialStoragePolicy> special_storage_policy) {
77  // TODO(jsbell): DCHECK that this is running on an IndexedDB thread,
78  // if a global handle to it is ever available.
79  std::vector<GURL> origins;
80  std::vector<base::FilePath> file_paths;
81  GetAllOriginsAndPaths(indexeddb_path, &origins, &file_paths);
82  DCHECK_EQ(origins.size(), file_paths.size());
83  std::vector<base::FilePath>::const_iterator file_path_iter =
84      file_paths.begin();
85  for (std::vector<GURL>::const_iterator iter = origins.begin();
86       iter != origins.end();
87       ++iter, ++file_path_iter) {
88    if (!special_storage_policy->IsStorageSessionOnly(*iter))
89      continue;
90    if (special_storage_policy->IsStorageProtected(*iter))
91      continue;
92    base::DeleteFile(*file_path_iter, true);
93  }
94}
95
96}  // namespace
97
98IndexedDBContextImpl::IndexedDBContextImpl(
99    const base::FilePath& data_path,
100    quota::SpecialStoragePolicy* special_storage_policy,
101    quota::QuotaManagerProxy* quota_manager_proxy,
102    base::SequencedTaskRunner* task_runner)
103    : force_keep_session_state_(false),
104      special_storage_policy_(special_storage_policy),
105      quota_manager_proxy_(quota_manager_proxy),
106      task_runner_(task_runner) {
107  if (!data_path.empty())
108    data_path_ = data_path.Append(kIndexedDBDirectory);
109  if (quota_manager_proxy) {
110    quota_manager_proxy->RegisterClient(new IndexedDBQuotaClient(this));
111  }
112}
113
114IndexedDBFactory* IndexedDBContextImpl::GetIDBFactory() {
115  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
116  if (!factory_) {
117    // Prime our cache of origins with existing databases so we can
118    // detect when dbs are newly created.
119    GetOriginSet();
120    factory_ = new IndexedDBFactory();
121  }
122  return factory_;
123}
124
125std::vector<GURL> IndexedDBContextImpl::GetAllOrigins() {
126  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
127  std::vector<GURL> origins;
128  std::set<GURL>* origins_set = GetOriginSet();
129  for (std::set<GURL>::const_iterator iter = origins_set->begin();
130       iter != origins_set->end();
131       ++iter) {
132    origins.push_back(*iter);
133  }
134  return origins;
135}
136
137std::vector<IndexedDBInfo> IndexedDBContextImpl::GetAllOriginsInfo() {
138  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
139  std::vector<GURL> origins = GetAllOrigins();
140  std::vector<IndexedDBInfo> result;
141  for (std::vector<GURL>::const_iterator iter = origins.begin();
142       iter != origins.end();
143       ++iter) {
144    const GURL& origin_url = *iter;
145
146    base::FilePath idb_directory = GetFilePath(origin_url);
147    size_t connection_count = GetConnectionCount(origin_url);
148    result.push_back(IndexedDBInfo(origin_url,
149                                   GetOriginDiskUsage(origin_url),
150                                   GetOriginLastModified(origin_url),
151                                   idb_directory,
152                                   connection_count));
153  }
154  return result;
155}
156
157static bool HostNameComparator(const GURL& i, const GURL& j) {
158  return i.host() < j.host();
159}
160
161ListValue* IndexedDBContextImpl::GetAllOriginsDetails() {
162  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
163  std::vector<GURL> origins = GetAllOrigins();
164
165  std::sort(origins.begin(), origins.end(), HostNameComparator);
166
167  scoped_ptr<ListValue> list(new ListValue());
168  for (std::vector<GURL>::const_iterator iter = origins.begin();
169       iter != origins.end();
170       ++iter) {
171    const GURL& origin_url = *iter;
172
173    scoped_ptr<DictionaryValue> info(new DictionaryValue());
174    info->SetString("url", origin_url.spec());
175    info->SetString("size", ui::FormatBytes(GetOriginDiskUsage(origin_url)));
176    info->SetDouble("last_modified",
177                    GetOriginLastModified(origin_url).ToJsTime());
178    info->SetString("path", GetFilePath(origin_url).value());
179    info->SetDouble("connection_count", GetConnectionCount(origin_url));
180
181    // This ends up being O(n^2) since we iterate over all open databases
182    // to extract just those in the origin, and we're iterating over all
183    // origins in the outer loop.
184
185    if (factory_) {
186      std::vector<IndexedDBDatabase*> databases =
187          factory_->GetOpenDatabasesForOrigin(
188              webkit_database::GetIdentifierFromOrigin(origin_url));
189      // TODO(jsbell): Sort by name?
190      scoped_ptr<ListValue> database_list(new ListValue());
191
192      for (std::vector<IndexedDBDatabase*>::iterator it = databases.begin();
193           it != databases.end();
194           ++it) {
195
196        const IndexedDBDatabase* db = *it;
197        scoped_ptr<DictionaryValue> db_info(new DictionaryValue());
198
199        db_info->SetString("name", db->name());
200        db_info->SetDouble("pending_opens", db->PendingOpenCount());
201        db_info->SetDouble("pending_upgrades", db->PendingUpgradeCount());
202        db_info->SetDouble("running_upgrades", db->RunningUpgradeCount());
203        db_info->SetDouble("pending_deletes", db->PendingDeleteCount());
204        db_info->SetDouble("connection_count",
205                           db->ConnectionCount() - db->PendingUpgradeCount() -
206                               db->RunningUpgradeCount());
207
208        scoped_ptr<ListValue> transaction_list(new ListValue());
209        std::vector<const IndexedDBTransaction*> transactions =
210            db->transaction_coordinator().GetTransactions();
211        for (std::vector<const IndexedDBTransaction*>::iterator trans_it =
212                 transactions.begin();
213             trans_it != transactions.end();
214             ++trans_it) {
215
216          const IndexedDBTransaction* transaction = *trans_it;
217          scoped_ptr<DictionaryValue> transaction_info(new DictionaryValue());
218
219          const char* kModes[] = { "readonly", "readwrite", "versionchange" };
220          transaction_info->SetString("mode", kModes[transaction->mode()]);
221          transaction_info->SetBoolean("running", transaction->IsRunning());
222
223          scoped_ptr<ListValue> scope(new ListValue());
224          for (std::set<int64>::const_iterator scope_it =
225                   transaction->scope().begin();
226               scope_it != transaction->scope().end();
227               ++scope_it) {
228            IndexedDBDatabaseMetadata::ObjectStoreMap::const_iterator it =
229                db->metadata().object_stores.find(*scope_it);
230            if (it != db->metadata().object_stores.end())
231              scope->AppendString(it->second.name);
232          }
233
234          transaction_info->Set("scope", scope.release());
235          transaction_list->Append(transaction_info.release());
236        }
237        db_info->Set("transactions", transaction_list.release());
238
239        database_list->Append(db_info.release());
240      }
241      info->Set("databases", database_list.release());
242    }
243
244    list->Append(info.release());
245  }
246  return list.release();
247}
248
249int64 IndexedDBContextImpl::GetOriginDiskUsage(const GURL& origin_url) {
250  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
251  if (data_path_.empty() || !IsInOriginSet(origin_url))
252    return 0;
253  EnsureDiskUsageCacheInitialized(origin_url);
254  return origin_size_map_[origin_url];
255}
256
257base::Time IndexedDBContextImpl::GetOriginLastModified(const GURL& origin_url) {
258  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
259  if (data_path_.empty() || !IsInOriginSet(origin_url))
260    return base::Time();
261  base::FilePath idb_directory = GetFilePath(origin_url);
262  base::PlatformFileInfo file_info;
263  if (!file_util::GetFileInfo(idb_directory, &file_info))
264    return base::Time();
265  return file_info.last_modified;
266}
267
268void IndexedDBContextImpl::DeleteForOrigin(const GURL& origin_url) {
269  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
270  ForceClose(origin_url);
271  if (data_path_.empty() || !IsInOriginSet(origin_url))
272    return;
273
274  base::FilePath idb_directory = GetFilePath(origin_url);
275  EnsureDiskUsageCacheInitialized(origin_url);
276  const bool recursive = true;
277  bool deleted = base::DeleteFile(idb_directory, recursive);
278
279  QueryDiskAndUpdateQuotaUsage(origin_url);
280  if (deleted) {
281    RemoveFromOriginSet(origin_url);
282    origin_size_map_.erase(origin_url);
283    space_available_map_.erase(origin_url);
284  }
285}
286
287void IndexedDBContextImpl::ForceClose(const GURL& origin_url) {
288  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
289  if (data_path_.empty() || !IsInOriginSet(origin_url))
290    return;
291
292  if (connections_.find(origin_url) != connections_.end()) {
293    ConnectionSet& connections = connections_[origin_url];
294    ConnectionSet::iterator it = connections.begin();
295    while (it != connections.end()) {
296      // Remove before closing so callbacks don't double-erase
297      IndexedDBConnection* connection = *it;
298      connections.erase(it++);
299      connection->ForceClose();
300    }
301    DCHECK_EQ(connections_[origin_url].size(), 0UL);
302    connections_.erase(origin_url);
303  }
304}
305
306size_t IndexedDBContextImpl::GetConnectionCount(const GURL& origin_url) {
307  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
308  if (data_path_.empty() || !IsInOriginSet(origin_url))
309    return 0;
310
311  if (connections_.find(origin_url) == connections_.end())
312    return 0;
313
314  return connections_[origin_url].size();
315}
316
317base::FilePath IndexedDBContextImpl::GetFilePath(const GURL& origin_url) {
318  std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
319  return GetIndexedDBFilePath(origin_id);
320}
321
322base::FilePath IndexedDBContextImpl::GetFilePathForTesting(
323    const std::string& origin_id) const {
324  return GetIndexedDBFilePath(origin_id);
325}
326
327void IndexedDBContextImpl::SetTaskRunnerForTesting(
328    base::SequencedTaskRunner* task_runner) {
329  DCHECK(!task_runner_);
330  task_runner_ = task_runner;
331}
332
333void IndexedDBContextImpl::ConnectionOpened(const GURL& origin_url,
334                                            IndexedDBConnection* connection) {
335  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
336  DCHECK_EQ(connections_[origin_url].count(connection), 0UL);
337  if (quota_manager_proxy()) {
338    quota_manager_proxy()->NotifyStorageAccessed(
339        quota::QuotaClient::kIndexedDatabase,
340        origin_url,
341        quota::kStorageTypeTemporary);
342  }
343  connections_[origin_url].insert(connection);
344  if (AddToOriginSet(origin_url)) {
345    // A newly created db, notify the quota system.
346    QueryDiskAndUpdateQuotaUsage(origin_url);
347  } else {
348    EnsureDiskUsageCacheInitialized(origin_url);
349  }
350  QueryAvailableQuota(origin_url);
351}
352
353void IndexedDBContextImpl::ConnectionClosed(const GURL& origin_url,
354                                            IndexedDBConnection* connection) {
355  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
356  // May not be in the map if connection was forced to close
357  if (connections_.find(origin_url) == connections_.end() ||
358      connections_[origin_url].count(connection) != 1)
359    return;
360  if (quota_manager_proxy()) {
361    quota_manager_proxy()->NotifyStorageAccessed(
362        quota::QuotaClient::kIndexedDatabase,
363        origin_url,
364        quota::kStorageTypeTemporary);
365  }
366  connections_[origin_url].erase(connection);
367  if (connections_[origin_url].size() == 0) {
368    QueryDiskAndUpdateQuotaUsage(origin_url);
369    connections_.erase(origin_url);
370  }
371}
372
373void IndexedDBContextImpl::TransactionComplete(const GURL& origin_url) {
374  DCHECK(connections_.find(origin_url) != connections_.end() &&
375         connections_[origin_url].size() > 0);
376  QueryDiskAndUpdateQuotaUsage(origin_url);
377  QueryAvailableQuota(origin_url);
378}
379
380bool IndexedDBContextImpl::WouldBeOverQuota(const GURL& origin_url,
381                                            int64 additional_bytes) {
382  if (space_available_map_.find(origin_url) == space_available_map_.end()) {
383    // We haven't heard back from the QuotaManager yet, just let it through.
384    return false;
385  }
386  bool over_quota = additional_bytes > space_available_map_[origin_url];
387  return over_quota;
388}
389
390bool IndexedDBContextImpl::IsOverQuota(const GURL& origin_url) {
391  const int kOneAdditionalByte = 1;
392  return WouldBeOverQuota(origin_url, kOneAdditionalByte);
393}
394
395quota::QuotaManagerProxy* IndexedDBContextImpl::quota_manager_proxy() {
396  return quota_manager_proxy_;
397}
398
399IndexedDBContextImpl::~IndexedDBContextImpl() {
400  if (factory_) {
401    IndexedDBFactory* factory = factory_;
402    factory->AddRef();
403    factory_ = NULL;
404    if (!task_runner_->ReleaseSoon(FROM_HERE, factory)) {
405      factory->Release();
406    }
407  }
408
409  if (data_path_.empty())
410    return;
411
412  if (force_keep_session_state_)
413    return;
414
415  bool has_session_only_databases =
416      special_storage_policy_ &&
417      special_storage_policy_->HasSessionOnlyOrigins();
418
419  // Clearning only session-only databases, and there are none.
420  if (!has_session_only_databases)
421    return;
422
423  TaskRunner()->PostTask(
424      FROM_HERE,
425      base::Bind(
426          &ClearSessionOnlyOrigins, data_path_, special_storage_policy_));
427}
428
429base::FilePath IndexedDBContextImpl::GetIndexedDBFilePath(
430    const std::string& origin_id) const {
431  DCHECK(!data_path_.empty());
432  return data_path_.AppendASCII(origin_id).AddExtension(kIndexedDBExtension)
433      .AddExtension(kLevelDBExtension);
434}
435
436int64 IndexedDBContextImpl::ReadUsageFromDisk(const GURL& origin_url) const {
437  if (data_path_.empty())
438    return 0;
439  std::string origin_id = webkit_database::GetIdentifierFromOrigin(origin_url);
440  base::FilePath file_path = GetIndexedDBFilePath(origin_id);
441  return base::ComputeDirectorySize(file_path);
442}
443
444void IndexedDBContextImpl::EnsureDiskUsageCacheInitialized(
445    const GURL& origin_url) {
446  if (origin_size_map_.find(origin_url) == origin_size_map_.end())
447    origin_size_map_[origin_url] = ReadUsageFromDisk(origin_url);
448}
449
450void IndexedDBContextImpl::QueryDiskAndUpdateQuotaUsage(
451    const GURL& origin_url) {
452  int64 former_disk_usage = origin_size_map_[origin_url];
453  int64 current_disk_usage = ReadUsageFromDisk(origin_url);
454  int64 difference = current_disk_usage - former_disk_usage;
455  if (difference) {
456    origin_size_map_[origin_url] = current_disk_usage;
457    // quota_manager_proxy() is NULL in unit tests.
458    if (quota_manager_proxy()) {
459      quota_manager_proxy()->NotifyStorageModified(
460          quota::QuotaClient::kIndexedDatabase,
461          origin_url,
462          quota::kStorageTypeTemporary,
463          difference);
464    }
465  }
466}
467
468void IndexedDBContextImpl::GotUsageAndQuota(const GURL& origin_url,
469                                            quota::QuotaStatusCode status,
470                                            int64 usage,
471                                            int64 quota) {
472  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
473  DCHECK(status == quota::kQuotaStatusOk || status == quota::kQuotaErrorAbort)
474      << "status was " << status;
475  if (status == quota::kQuotaErrorAbort) {
476    // We seem to no longer care to wait around for the answer.
477    return;
478  }
479  TaskRunner()->PostTask(FROM_HERE,
480                         base::Bind(&IndexedDBContextImpl::GotUpdatedQuota,
481                                    this,
482                                    origin_url,
483                                    usage,
484                                    quota));
485}
486
487void IndexedDBContextImpl::GotUpdatedQuota(const GURL& origin_url,
488                                           int64 usage,
489                                           int64 quota) {
490  DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
491  space_available_map_[origin_url] = quota - usage;
492}
493
494void IndexedDBContextImpl::QueryAvailableQuota(const GURL& origin_url) {
495  if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
496    DCHECK(TaskRunner()->RunsTasksOnCurrentThread());
497    if (quota_manager_proxy()) {
498      BrowserThread::PostTask(
499          BrowserThread::IO,
500          FROM_HERE,
501          base::Bind(
502              &IndexedDBContextImpl::QueryAvailableQuota, this, origin_url));
503    }
504    return;
505  }
506  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
507  if (!quota_manager_proxy() || !quota_manager_proxy()->quota_manager())
508    return;
509  quota_manager_proxy()->quota_manager()->GetUsageAndQuota(
510      origin_url,
511      quota::kStorageTypeTemporary,
512      base::Bind(&IndexedDBContextImpl::GotUsageAndQuota, this, origin_url));
513}
514
515std::set<GURL>* IndexedDBContextImpl::GetOriginSet() {
516  if (!origin_set_) {
517    origin_set_.reset(new std::set<GURL>);
518    std::vector<GURL> origins;
519    GetAllOriginsAndPaths(data_path_, &origins, NULL);
520    for (std::vector<GURL>::const_iterator iter = origins.begin();
521         iter != origins.end();
522         ++iter) {
523      origin_set_->insert(*iter);
524    }
525  }
526  return origin_set_.get();
527}
528
529void IndexedDBContextImpl::ResetCaches() {
530  origin_set_.reset();
531  origin_size_map_.clear();
532  space_available_map_.clear();
533}
534
535base::TaskRunner* IndexedDBContextImpl::TaskRunner() const {
536  return task_runner_;
537}
538
539}  // namespace content
540