/*
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "IDBDatabaseBackendImpl.h"
#if ENABLE(INDEXED_DATABASE)
#include "CrossThreadTask.h"
#include "DOMStringList.h"
#include "IDBBackingStore.h"
#include "IDBDatabaseException.h"
#include "IDBFactoryBackendImpl.h"
#include "IDBObjectStoreBackendImpl.h"
#include "IDBTransactionBackendImpl.h"
#include "IDBTransactionCoordinator.h"
namespace WebCore {
class IDBDatabaseBackendImpl::PendingSetVersionCall : public RefCounted<PendingSetVersionCall> {
public:
static PassRefPtr<PendingSetVersionCall> create(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
{
return adoptRef(new PendingSetVersionCall(version, callbacks, databaseCallbacks));
}
String version() { return m_version; }
PassRefPtr<IDBCallbacks> callbacks() { return m_callbacks; }
PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks() { return m_databaseCallbacks; }
private:
PendingSetVersionCall(const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBDatabaseCallbacks> databaseCallbacks)
: m_version(version)
, m_callbacks(callbacks)
, m_databaseCallbacks(databaseCallbacks)
{
}
String m_version;
RefPtr<IDBCallbacks> m_callbacks;
RefPtr<IDBDatabaseCallbacks> m_databaseCallbacks;
};
IDBDatabaseBackendImpl::IDBDatabaseBackendImpl(const String& name, IDBBackingStore* backingStore, IDBTransactionCoordinator* coordinator, IDBFactoryBackendImpl* factory, const String& uniqueIdentifier)
: m_backingStore(backingStore)
, m_id(InvalidId)
, m_name(name)
, m_version("")
, m_identifier(uniqueIdentifier)
, m_factory(factory)
, m_transactionCoordinator(coordinator)
{
ASSERT(!m_name.isNull());
bool success = m_backingStore->extractIDBDatabaseMetaData(m_name, m_version, m_id);
ASSERT_UNUSED(success, success == (m_id != InvalidId));
if (!m_backingStore->setIDBDatabaseMetaData(m_name, m_version, m_id, m_id == InvalidId))
ASSERT_NOT_REACHED(); // FIXME: Need better error handling.
loadObjectStores();
}
IDBDatabaseBackendImpl::~IDBDatabaseBackendImpl()
{
m_factory->removeIDBDatabaseBackend(m_identifier);
}
PassRefPtr<IDBBackingStore> IDBDatabaseBackendImpl::backingStore() const
{
return m_backingStore;
}
PassRefPtr<DOMStringList> IDBDatabaseBackendImpl::objectStoreNames() const
{
RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
for (ObjectStoreMap::const_iterator it = m_objectStores.begin(); it != m_objectStores.end(); ++it)
objectStoreNames->append(it->first);
return objectStoreNames.release();
}
PassRefPtr<IDBObjectStoreBackendInterface> IDBDatabaseBackendImpl::createObjectStore(const String& name, const String& keyPath, bool autoIncrement, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec)
{
ASSERT(transactionPtr->mode() == IDBTransaction::VERSION_CHANGE);
if (m_objectStores.contains(name)) {
ec = IDBDatabaseException::CONSTRAINT_ERR;
return 0;
}
RefPtr<IDBObjectStoreBackendImpl> objectStore = IDBObjectStoreBackendImpl::create(m_backingStore.get(), m_id, name, keyPath, autoIncrement);
ASSERT(objectStore->name() == name);
RefPtr<IDBDatabaseBackendImpl> database = this;
RefPtr<IDBTransactionBackendInterface> transaction = transactionPtr;
if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::createObjectStoreInternal, database, objectStore, transaction),
createCallbackTask(&IDBDatabaseBackendImpl::removeObjectStoreFromMap, database, objectStore))) {
ec = IDBDatabaseException::NOT_ALLOWED_ERR;
return 0;
}
m_objectStores.set(name, objectStore);
return objectStore.release();
}
void IDBDatabaseBackendImpl::createObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore, PassRefPtr<IDBTransactionBackendInterface> transaction)
{
int64_t objectStoreId;
if (!database->m_backingStore->createObjectStore(database->id(), objectStore->name(), objectStore->keyPath(), objectStore->autoIncrement(), objectStoreId)) {
transaction->abort();
return;
}
objectStore->setId(objectStoreId);
transaction->didCompleteTaskEvents();
}
PassRefPtr<IDBObjectStoreBackendInterface> IDBDatabaseBackendImpl::objectStore(const String& name)
{
return m_objectStores.get(name);
}
void IDBDatabaseBackendImpl::deleteObjectStore(const String& name, IDBTransactionBackendInterface* transactionPtr, ExceptionCode& ec)
{
RefPtr<IDBObjectStoreBackendImpl> objectStore = m_objectStores.get(name);
if (!objectStore) {
ec = IDBDatabaseException::NOT_FOUND_ERR;
return;
}
RefPtr<IDBDatabaseBackendImpl> database = this;
RefPtr<IDBTransactionBackendInterface> transaction = transactionPtr;
if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::deleteObjectStoreInternal, database, objectStore, transaction),
createCallbackTask(&IDBDatabaseBackendImpl::addObjectStoreToMap, database, objectStore))) {
ec = IDBDatabaseException::NOT_ALLOWED_ERR;
return;
}
m_objectStores.remove(name);
}
void IDBDatabaseBackendImpl::deleteObjectStoreInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore, PassRefPtr<IDBTransactionBackendInterface> transaction)
{
database->m_backingStore->deleteObjectStore(database->id(), objectStore->id());
transaction->didCompleteTaskEvents();
}
void IDBDatabaseBackendImpl::setVersion(const String& version, PassRefPtr<IDBCallbacks> prpCallbacks, PassRefPtr<IDBDatabaseCallbacks> prpDatabaseCallbacks, ExceptionCode& ec)
{
RefPtr<IDBCallbacks> callbacks = prpCallbacks;
RefPtr<IDBDatabaseCallbacks> databaseCallbacks = prpDatabaseCallbacks;
if (!m_databaseCallbacksSet.contains(databaseCallbacks)) {
callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::ABORT_ERR, "Connection was closed before set version transaction was created"));
return;
}
for (DatabaseCallbacksSet::const_iterator it = m_databaseCallbacksSet.begin(); it != m_databaseCallbacksSet.end(); ++it) {
if (*it != databaseCallbacks)
(*it)->onVersionChange(version);
}
if (m_databaseCallbacksSet.size() > 1) {
callbacks->onBlocked();
RefPtr<PendingSetVersionCall> pendingSetVersionCall = PendingSetVersionCall::create(version, callbacks, databaseCallbacks);
m_pendingSetVersionCalls.append(pendingSetVersionCall);
return;
}
RefPtr<DOMStringList> objectStoreNames = DOMStringList::create();
RefPtr<IDBDatabaseBackendImpl> database = this;
RefPtr<IDBTransactionBackendInterface> transaction = IDBTransactionBackendImpl::create(objectStoreNames.get(), IDBTransaction::VERSION_CHANGE, this);
if (!transaction->scheduleTask(createCallbackTask(&IDBDatabaseBackendImpl::setVersionInternal, database, version, callbacks, transaction),
createCallbackTask(&IDBDatabaseBackendImpl::resetVersion, database, m_version))) {
ec = IDBDatabaseException::NOT_ALLOWED_ERR;
}
}
void IDBDatabaseBackendImpl::setVersionInternal(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, const String& version, PassRefPtr<IDBCallbacks> callbacks, PassRefPtr<IDBTransactionBackendInterface> transaction)
{
int64_t databaseId = database->id();
database->m_version = version;
if (!database->m_backingStore->setIDBDatabaseMetaData(database->m_name, database->m_version, databaseId, databaseId == InvalidId)) {
// FIXME: The Indexed Database specification does not have an error code dedicated to I/O errors.
callbacks->onError(IDBDatabaseError::create(IDBDatabaseException::UNKNOWN_ERR, "Error writing data to stable storage."));
transaction->abort();
return;
}
callbacks->onSuccess(transaction);
}
PassRefPtr<IDBTransactionBackendInterface> IDBDatabaseBackendImpl::transaction(DOMStringList* objectStoreNames, unsigned short mode, ExceptionCode& ec)
{
for (size_t i = 0; i < objectStoreNames->length(); ++i) {
if (!m_objectStores.contains(objectStoreNames->item(i))) {
ec = IDBDatabaseException::NOT_FOUND_ERR;
return 0;
}
}
// FIXME: Return not allowed err if close has been called.
return IDBTransactionBackendImpl::create(objectStoreNames, mode, this);
}
void IDBDatabaseBackendImpl::open(PassRefPtr<IDBDatabaseCallbacks> callbacks)
{
m_databaseCallbacksSet.add(RefPtr<IDBDatabaseCallbacks>(callbacks));
}
void IDBDatabaseBackendImpl::close(PassRefPtr<IDBDatabaseCallbacks> prpCallbacks)
{
RefPtr<IDBDatabaseCallbacks> callbacks = prpCallbacks;
ASSERT(m_databaseCallbacksSet.contains(callbacks));
m_databaseCallbacksSet.remove(callbacks);
if (m_databaseCallbacksSet.size() > 1)
return;
while (!m_pendingSetVersionCalls.isEmpty()) {
ExceptionCode ec = 0;
RefPtr<PendingSetVersionCall> pendingSetVersionCall = m_pendingSetVersionCalls.takeFirst();
setVersion(pendingSetVersionCall->version(), pendingSetVersionCall->callbacks(), pendingSetVersionCall->databaseCallbacks(), ec);
ASSERT(!ec);
}
}
void IDBDatabaseBackendImpl::loadObjectStores()
{
Vector<int64_t> ids;
Vector<String> names;
Vector<String> keyPaths;
Vector<bool> autoIncrementFlags;
m_backingStore->getObjectStores(m_id, ids, names, keyPaths, autoIncrementFlags);
ASSERT(names.size() == ids.size());
ASSERT(keyPaths.size() == ids.size());
ASSERT(autoIncrementFlags.size() == ids.size());
for (size_t i = 0; i < ids.size(); i++)
m_objectStores.set(names[i], IDBObjectStoreBackendImpl::create(m_backingStore.get(), m_id, ids[i], names[i], keyPaths[i], autoIncrementFlags[i]));
}
void IDBDatabaseBackendImpl::removeObjectStoreFromMap(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore)
{
ASSERT(database->m_objectStores.contains(objectStore->name()));
database->m_objectStores.remove(objectStore->name());
}
void IDBDatabaseBackendImpl::addObjectStoreToMap(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, PassRefPtr<IDBObjectStoreBackendImpl> objectStore)
{
RefPtr<IDBObjectStoreBackendImpl> objectStorePtr = objectStore;
ASSERT(!database->m_objectStores.contains(objectStorePtr->name()));
database->m_objectStores.set(objectStorePtr->name(), objectStorePtr);
}
void IDBDatabaseBackendImpl::resetVersion(ScriptExecutionContext*, PassRefPtr<IDBDatabaseBackendImpl> database, const String& version)
{
database->m_version = version;
}
} // namespace WebCore
#endif // ENABLE(INDEXED_DATABASE)