1// Copyright (c) 2013 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_factory_impl.h"
6
7#include <utility>
8#include <vector>
9
10#include "base/logging.h"
11#include "base/strings/utf_string_conversions.h"
12#include "base/time/time.h"
13#include "content/browser/indexed_db/indexed_db_backing_store.h"
14#include "content/browser/indexed_db/indexed_db_context_impl.h"
15#include "content/browser/indexed_db/indexed_db_database_error.h"
16#include "content/browser/indexed_db/indexed_db_tracing.h"
17#include "content/browser/indexed_db/indexed_db_transaction_coordinator.h"
18#include "storage/common/database/database_identifier.h"
19#include "third_party/WebKit/public/platform/WebIDBDatabaseException.h"
20#include "third_party/leveldatabase/env_chromium.h"
21
22using base::ASCIIToUTF16;
23
24namespace content {
25
26const int64 kBackingStoreGracePeriodMs = 2000;
27
28IndexedDBFactoryImpl::IndexedDBFactoryImpl(IndexedDBContextImpl* context)
29    : context_(context) {
30}
31
32IndexedDBFactoryImpl::~IndexedDBFactoryImpl() {
33}
34
35void IndexedDBFactoryImpl::RemoveDatabaseFromMaps(
36    const IndexedDBDatabase::Identifier& identifier) {
37  IndexedDBDatabaseMap::iterator it = database_map_.find(identifier);
38  DCHECK(it != database_map_.end());
39  IndexedDBDatabase* database = it->second;
40  database_map_.erase(it);
41
42  std::pair<OriginDBMap::iterator, OriginDBMap::iterator> range =
43      origin_dbs_.equal_range(database->identifier().first);
44  DCHECK(range.first != range.second);
45  for (OriginDBMap::iterator it2 = range.first; it2 != range.second; ++it2) {
46    if (it2->second == database) {
47      origin_dbs_.erase(it2);
48      break;
49    }
50  }
51}
52
53void IndexedDBFactoryImpl::ReleaseDatabase(
54    const IndexedDBDatabase::Identifier& identifier,
55    bool forcedClose) {
56  DCHECK(!database_map_.find(identifier)->second->backing_store());
57
58  RemoveDatabaseFromMaps(identifier);
59
60  // No grace period on a forced-close, as the initiator is
61  // assuming the backing store will be released once all
62  // connections are closed.
63  ReleaseBackingStore(identifier.first, forcedClose);
64}
65
66void IndexedDBFactoryImpl::ReleaseBackingStore(const GURL& origin_url,
67                                               bool immediate) {
68  if (immediate) {
69    IndexedDBBackingStoreMap::iterator it =
70        backing_stores_with_active_blobs_.find(origin_url);
71    if (it != backing_stores_with_active_blobs_.end()) {
72      it->second->active_blob_registry()->ForceShutdown();
73      backing_stores_with_active_blobs_.erase(it);
74    }
75  }
76
77  // Only close if this is the last reference.
78  if (!HasLastBackingStoreReference(origin_url))
79    return;
80
81  // If this factory does hold the last reference to the backing store, it can
82  // be closed - but unless requested to close it immediately, keep it around
83  // for a short period so that a re-open is fast.
84  if (immediate) {
85    CloseBackingStore(origin_url);
86    return;
87  }
88
89  // Start a timer to close the backing store, unless something else opens it
90  // in the mean time.
91  DCHECK(!backing_store_map_[origin_url]->close_timer()->IsRunning());
92  backing_store_map_[origin_url]->close_timer()->Start(
93      FROM_HERE,
94      base::TimeDelta::FromMilliseconds(kBackingStoreGracePeriodMs),
95      base::Bind(
96          &IndexedDBFactoryImpl::MaybeCloseBackingStore, this, origin_url));
97}
98
99void IndexedDBFactoryImpl::MaybeCloseBackingStore(const GURL& origin_url) {
100  // Another reference may have opened since the maybe-close was posted, so it
101  // is necessary to check again.
102  if (HasLastBackingStoreReference(origin_url))
103    CloseBackingStore(origin_url);
104}
105
106void IndexedDBFactoryImpl::CloseBackingStore(const GURL& origin_url) {
107  IndexedDBBackingStoreMap::iterator it = backing_store_map_.find(origin_url);
108  DCHECK(it != backing_store_map_.end());
109  // Stop the timer (if it's running) - this may happen if the timer was started
110  // and then a forced close occurs.
111  it->second->close_timer()->Stop();
112  backing_store_map_.erase(it);
113}
114
115bool IndexedDBFactoryImpl::HasLastBackingStoreReference(
116    const GURL& origin_url) const {
117  IndexedDBBackingStore* ptr;
118  {
119    // Scope so that the implicit scoped_refptr<> is freed.
120    IndexedDBBackingStoreMap::const_iterator it =
121        backing_store_map_.find(origin_url);
122    DCHECK(it != backing_store_map_.end());
123    ptr = it->second.get();
124  }
125  return ptr->HasOneRef();
126}
127
128void IndexedDBFactoryImpl::ForceClose(const GURL& origin_url) {
129  OriginDBs range = GetOpenDatabasesForOrigin(origin_url);
130
131  while (range.first != range.second) {
132    IndexedDBDatabase* db = range.first->second;
133    ++range.first;
134    db->ForceClose();
135  }
136
137  if (backing_store_map_.find(origin_url) != backing_store_map_.end())
138    ReleaseBackingStore(origin_url, true /* immediate */);
139}
140
141void IndexedDBFactoryImpl::ContextDestroyed() {
142  // Timers on backing stores hold a reference to this factory. When the
143  // context (which nominally owns this factory) is destroyed during thread
144  // termination the timers must be stopped so that this factory and the
145  // stores can be disposed of.
146  for (IndexedDBBackingStoreMap::iterator it = backing_store_map_.begin();
147       it != backing_store_map_.end();
148       ++it)
149    it->second->close_timer()->Stop();
150  backing_store_map_.clear();
151  backing_stores_with_active_blobs_.clear();
152  context_ = NULL;
153}
154
155void IndexedDBFactoryImpl::ReportOutstandingBlobs(const GURL& origin_url,
156                                                  bool blobs_outstanding) {
157  if (!context_)
158    return;
159  if (blobs_outstanding) {
160    DCHECK(!backing_stores_with_active_blobs_.count(origin_url));
161    IndexedDBBackingStoreMap::iterator it = backing_store_map_.find(origin_url);
162    if (it != backing_store_map_.end())
163      backing_stores_with_active_blobs_.insert(*it);
164    else
165      DCHECK(false);
166  } else {
167    IndexedDBBackingStoreMap::iterator it =
168        backing_stores_with_active_blobs_.find(origin_url);
169    if (it != backing_stores_with_active_blobs_.end()) {
170      backing_stores_with_active_blobs_.erase(it);
171      ReleaseBackingStore(origin_url, false /* immediate */);
172    }
173  }
174}
175
176void IndexedDBFactoryImpl::GetDatabaseNames(
177    scoped_refptr<IndexedDBCallbacks> callbacks,
178    const GURL& origin_url,
179    const base::FilePath& data_directory,
180    net::URLRequestContext* request_context) {
181  IDB_TRACE("IndexedDBFactoryImpl::GetDatabaseNames");
182  // TODO(dgrogan): Plumb data_loss back to script eventually?
183  blink::WebIDBDataLoss data_loss;
184  std::string data_loss_message;
185  bool disk_full;
186  leveldb::Status s;
187  // TODO(cmumford): Handle this error
188  scoped_refptr<IndexedDBBackingStore> backing_store =
189      OpenBackingStore(origin_url,
190                       data_directory,
191                       request_context,
192                       &data_loss,
193                       &data_loss_message,
194                       &disk_full,
195                       &s);
196  if (!backing_store.get()) {
197    callbacks->OnError(
198        IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionUnknownError,
199                               "Internal error opening backing store for "
200                               "indexedDB.webkitGetDatabaseNames."));
201    return;
202  }
203
204  std::vector<base::string16> names = backing_store->GetDatabaseNames(&s);
205  if (!s.ok()) {
206    DLOG(ERROR) << "Internal error getting database names";
207    IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
208                                 "Internal error opening backing store for "
209                                 "indexedDB.webkitGetDatabaseNames.");
210    callbacks->OnError(error);
211    backing_store = NULL;
212    if (s.IsCorruption())
213      HandleBackingStoreCorruption(origin_url, error);
214    return;
215  }
216  callbacks->OnSuccess(names);
217  backing_store = NULL;
218  ReleaseBackingStore(origin_url, false /* immediate */);
219}
220
221void IndexedDBFactoryImpl::DeleteDatabase(
222    const base::string16& name,
223    net::URLRequestContext* request_context,
224    scoped_refptr<IndexedDBCallbacks> callbacks,
225    const GURL& origin_url,
226    const base::FilePath& data_directory) {
227  IDB_TRACE("IndexedDBFactoryImpl::DeleteDatabase");
228  IndexedDBDatabase::Identifier unique_identifier(origin_url, name);
229  IndexedDBDatabaseMap::iterator it = database_map_.find(unique_identifier);
230  if (it != database_map_.end()) {
231    // If there are any connections to the database, directly delete the
232    // database.
233    it->second->DeleteDatabase(callbacks);
234    return;
235  }
236
237  // TODO(dgrogan): Plumb data_loss back to script eventually?
238  blink::WebIDBDataLoss data_loss;
239  std::string data_loss_message;
240  bool disk_full = false;
241  leveldb::Status s;
242  scoped_refptr<IndexedDBBackingStore> backing_store =
243      OpenBackingStore(origin_url,
244                       data_directory,
245                       request_context,
246                       &data_loss,
247                       &data_loss_message,
248                       &disk_full,
249                       &s);
250  if (!backing_store.get()) {
251    IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
252                                 ASCIIToUTF16(
253                                     "Internal error opening backing store "
254                                     "for indexedDB.deleteDatabase."));
255    callbacks->OnError(error);
256    if (s.IsCorruption()) {
257      HandleBackingStoreCorruption(origin_url, error);
258    }
259    return;
260  }
261
262  scoped_refptr<IndexedDBDatabase> database = IndexedDBDatabase::Create(
263      name, backing_store.get(), this, unique_identifier, &s);
264  if (!database.get()) {
265    IndexedDBDatabaseError error(
266        blink::WebIDBDatabaseExceptionUnknownError,
267        ASCIIToUTF16(
268            "Internal error creating database backend for "
269            "indexedDB.deleteDatabase."));
270    callbacks->OnError(error);
271    if (leveldb_env::IsCorruption(s)) {
272      backing_store = NULL;
273      HandleBackingStoreCorruption(origin_url, error);
274    }
275    return;
276  }
277
278  database_map_[unique_identifier] = database.get();
279  origin_dbs_.insert(std::make_pair(origin_url, database.get()));
280  database->DeleteDatabase(callbacks);
281  RemoveDatabaseFromMaps(unique_identifier);
282  database = NULL;
283  backing_store = NULL;
284  ReleaseBackingStore(origin_url, false /* immediate */);
285}
286
287void IndexedDBFactoryImpl::DatabaseDeleted(
288    const IndexedDBDatabase::Identifier& identifier) {
289  // NULL after ContextDestroyed() called, and in some unit tests.
290  if (!context_)
291    return;
292  context_->DatabaseDeleted(identifier.first);
293}
294
295void IndexedDBFactoryImpl::HandleBackingStoreFailure(const GURL& origin_url) {
296  // NULL after ContextDestroyed() called, and in some unit tests.
297  if (!context_)
298    return;
299  context_->ForceClose(origin_url,
300                       IndexedDBContextImpl::FORCE_CLOSE_BACKING_STORE_FAILURE);
301}
302
303void IndexedDBFactoryImpl::HandleBackingStoreCorruption(
304    const GURL& origin_url,
305    const IndexedDBDatabaseError& error) {
306  // Make a copy of origin_url as this is likely a reference to a member of a
307  // backing store which this function will be deleting.
308  GURL saved_origin_url(origin_url);
309  DCHECK(context_);
310  base::FilePath path_base = context_->data_path();
311  IndexedDBBackingStore::RecordCorruptionInfo(
312      path_base, saved_origin_url, base::UTF16ToUTF8(error.message()));
313  HandleBackingStoreFailure(saved_origin_url);
314  // Note: DestroyBackingStore only deletes LevelDB files, leaving all others,
315  //       so our corruption info file will remain.
316  leveldb::Status s =
317      IndexedDBBackingStore::DestroyBackingStore(path_base, saved_origin_url);
318  if (!s.ok())
319    DLOG(ERROR) << "Unable to delete backing store: " << s.ToString();
320}
321
322bool IndexedDBFactoryImpl::IsDatabaseOpen(const GURL& origin_url,
323                                          const base::string16& name) const {
324  return !!database_map_.count(IndexedDBDatabase::Identifier(origin_url, name));
325}
326
327bool IndexedDBFactoryImpl::IsBackingStoreOpen(const GURL& origin_url) const {
328  return backing_store_map_.find(origin_url) != backing_store_map_.end();
329}
330
331bool IndexedDBFactoryImpl::IsBackingStorePendingClose(
332    const GURL& origin_url) const {
333  IndexedDBBackingStoreMap::const_iterator it =
334      backing_store_map_.find(origin_url);
335  if (it == backing_store_map_.end())
336    return false;
337  return it->second->close_timer()->IsRunning();
338}
339
340scoped_refptr<IndexedDBBackingStore>
341IndexedDBFactoryImpl::OpenBackingStoreHelper(
342    const GURL& origin_url,
343    const base::FilePath& data_directory,
344    net::URLRequestContext* request_context,
345    blink::WebIDBDataLoss* data_loss,
346    std::string* data_loss_message,
347    bool* disk_full,
348    bool first_time,
349    leveldb::Status* status) {
350  return IndexedDBBackingStore::Open(this,
351                                     origin_url,
352                                     data_directory,
353                                     request_context,
354                                     data_loss,
355                                     data_loss_message,
356                                     disk_full,
357                                     context_->TaskRunner(),
358                                     first_time,
359                                     status);
360}
361
362scoped_refptr<IndexedDBBackingStore> IndexedDBFactoryImpl::OpenBackingStore(
363    const GURL& origin_url,
364    const base::FilePath& data_directory,
365    net::URLRequestContext* request_context,
366    blink::WebIDBDataLoss* data_loss,
367    std::string* data_loss_message,
368    bool* disk_full,
369    leveldb::Status* status) {
370  const bool open_in_memory = data_directory.empty();
371
372  IndexedDBBackingStoreMap::iterator it2 = backing_store_map_.find(origin_url);
373  if (it2 != backing_store_map_.end()) {
374    it2->second->close_timer()->Stop();
375    return it2->second;
376  }
377
378  scoped_refptr<IndexedDBBackingStore> backing_store;
379  bool first_time = false;
380  if (open_in_memory) {
381    backing_store = IndexedDBBackingStore::OpenInMemory(
382        origin_url, context_->TaskRunner(), status);
383  } else {
384    first_time = !backends_opened_since_boot_.count(origin_url);
385
386    backing_store = OpenBackingStoreHelper(origin_url,
387                                           data_directory,
388                                           request_context,
389                                           data_loss,
390                                           data_loss_message,
391                                           disk_full,
392                                           first_time,
393                                           status);
394  }
395
396  if (backing_store.get()) {
397    if (first_time)
398      backends_opened_since_boot_.insert(origin_url);
399    backing_store_map_[origin_url] = backing_store;
400    // If an in-memory database, bind lifetime to this factory instance.
401    if (open_in_memory)
402      session_only_backing_stores_.insert(backing_store);
403
404    // All backing stores associated with this factory should be of the same
405    // type.
406    DCHECK_NE(session_only_backing_stores_.empty(), open_in_memory);
407
408    return backing_store;
409  }
410
411  return 0;
412}
413
414void IndexedDBFactoryImpl::Open(const base::string16& name,
415                                const IndexedDBPendingConnection& connection,
416                                net::URLRequestContext* request_context,
417                                const GURL& origin_url,
418                                const base::FilePath& data_directory) {
419  IDB_TRACE("IndexedDBFactoryImpl::Open");
420  scoped_refptr<IndexedDBDatabase> database;
421  IndexedDBDatabase::Identifier unique_identifier(origin_url, name);
422  IndexedDBDatabaseMap::iterator it = database_map_.find(unique_identifier);
423  blink::WebIDBDataLoss data_loss =
424      blink::WebIDBDataLossNone;
425  std::string data_loss_message;
426  bool disk_full = false;
427  bool was_open = (it != database_map_.end());
428  if (!was_open) {
429    leveldb::Status s;
430    scoped_refptr<IndexedDBBackingStore> backing_store =
431        OpenBackingStore(origin_url,
432                         data_directory,
433                         request_context,
434                         &data_loss,
435                         &data_loss_message,
436                         &disk_full,
437                         &s);
438    if (!backing_store.get()) {
439      if (disk_full) {
440        connection.callbacks->OnError(
441            IndexedDBDatabaseError(blink::WebIDBDatabaseExceptionQuotaError,
442                                   ASCIIToUTF16(
443                                       "Encountered full disk while opening "
444                                       "backing store for indexedDB.open.")));
445        return;
446      }
447      IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
448                                   ASCIIToUTF16(
449                                       "Internal error opening backing store"
450                                       " for indexedDB.open."));
451      connection.callbacks->OnError(error);
452      if (s.IsCorruption()) {
453        HandleBackingStoreCorruption(origin_url, error);
454      }
455      return;
456    }
457
458    database = IndexedDBDatabase::Create(
459        name, backing_store.get(), this, unique_identifier, &s);
460    if (!database.get()) {
461      DLOG(ERROR) << "Unable to create the database";
462      IndexedDBDatabaseError error(blink::WebIDBDatabaseExceptionUnknownError,
463                                   ASCIIToUTF16(
464                                       "Internal error creating "
465                                       "database backend for "
466                                       "indexedDB.open."));
467      connection.callbacks->OnError(error);
468      if (leveldb_env::IsCorruption(s)) {
469        backing_store = NULL;  // Closes the LevelDB so that it can be deleted
470        HandleBackingStoreCorruption(origin_url, error);
471      }
472      return;
473    }
474  } else {
475    database = it->second;
476  }
477
478  if (data_loss != blink::WebIDBDataLossNone)
479    connection.callbacks->OnDataLoss(data_loss, data_loss_message);
480
481  database->OpenConnection(connection);
482
483  if (!was_open && database->ConnectionCount() > 0) {
484    database_map_[unique_identifier] = database.get();
485    origin_dbs_.insert(std::make_pair(origin_url, database.get()));
486  }
487}
488
489std::pair<IndexedDBFactoryImpl::OriginDBMapIterator,
490          IndexedDBFactoryImpl::OriginDBMapIterator>
491IndexedDBFactoryImpl::GetOpenDatabasesForOrigin(const GURL& origin_url) const {
492  return origin_dbs_.equal_range(origin_url);
493}
494
495size_t IndexedDBFactoryImpl::GetConnectionCount(const GURL& origin_url) const {
496  size_t count(0);
497
498  OriginDBs range = GetOpenDatabasesForOrigin(origin_url);
499  for (OriginDBMapIterator it = range.first; it != range.second; ++it)
500    count += it->second->ConnectionCount();
501
502  return count;
503}
504
505}  // namespace content
506