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