1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the test suite of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
41 #include <QStringList>
42 #include <QtQml/qqmlextensionplugin.h>
43 #include <QtQml/qqml.h>
44 #include <private/qqmlengine_p.h>
46 #include <private/qv8engine_p.h>
47 #include <QtSql/qsqldatabase.h>
48 #include <QtSql/qsqlquery.h>
49 #include <QtSql/qsqlerror.h>
50 #include <QtSql/qsqlrecord.h>
51 #include <QtSql/qsqlfield.h>
52 #include <QtCore/qstandardpaths.h>
53 #include <QtCore/qstack.h>
54 #include <QtCore/qcryptographichash.h>
55 #include <QtCore/qsettings.h>
56 #include <QtCore/qdir.h>
57 #include <private/qv8sqlerrors_p.h>
60 #define V8THROW_SQL(error, desc) \
62 v8::Local<v8::Value> v = v8::Exception::Error(engine->toString(desc)); \
63 v->ToObject()->Set(v8::String::New("code"), v8::Integer::New(error)); \
64 v8::ThrowException(v); \
65 return v8::Handle<v8::Value>(); \
68 #define V8THROW_SQL_VOID(error, desc) \
70 v8::Local<v8::Value> v = v8::Exception::Error(engine->toString(desc)); \
71 v->ToObject()->Set(v8::String::New("code"), v8::Integer::New(error)); \
72 v8::ThrowException(v); \
76 #define V8THROW_REFERENCE(string) { \
77 v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
78 return v8::Handle<v8::Value>(); \
81 #define V8THROW_REFERENCE_VOID(string) { \
82 v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
86 class QQmlSqlDatabaseData : public QV8Engine::Deletable
89 QQmlSqlDatabaseData(QV8Engine *engine);
90 ~QQmlSqlDatabaseData();
92 v8::Persistent<v8::Function> constructor;
93 v8::Persistent<v8::Function> queryConstructor;
94 v8::Persistent<v8::Function> rowsConstructor;
97 V8_DEFINE_EXTENSION(QQmlSqlDatabaseData, databaseData)
99 class QV8SqlDatabaseResource : public QV8ObjectResource
101 V8_RESOURCE_TYPE(SQLDatabaseType)
104 enum Type { Database, Query, Rows };
105 QV8SqlDatabaseResource(QV8Engine *e)
106 : QV8ObjectResource(e), type(Database), inTransaction(false), readonly(false), forwardOnly(false) {}
108 ~QV8SqlDatabaseResource() {
112 QSqlDatabase database;
114 QString version; // type == Database
116 bool inTransaction; // type == Query
117 bool readonly; // type == Query
119 QSqlQuery query; // type == Rows
120 bool forwardOnly; // type == Rows
123 static v8::Handle<v8::Value> qmlsqldatabase_version(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
125 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
126 if (!r || r->type != QV8SqlDatabaseResource::Database)
127 V8THROW_REFERENCE("Not a SQLDatabase object");
129 return r->engine->toString(r->version);
132 static v8::Handle<v8::Value> qmlsqldatabase_rows_length(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
134 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
135 if (!r || r->type != QV8SqlDatabaseResource::Rows)
136 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
138 int s = r->query.size();
141 if (r->query.last()) {
142 s = r->query.at() + 1;
147 return v8::Integer::New(s);
150 static v8::Handle<v8::Value> qmlsqldatabase_rows_forwardOnly(v8::Local<v8::String> /* property */,
151 const v8::AccessorInfo& info)
153 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
154 if (!r || r->type != QV8SqlDatabaseResource::Rows)
155 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
156 return v8::Boolean::New(r->query.isForwardOnly());
159 static void qmlsqldatabase_rows_setForwardOnly(v8::Local<v8::String> /* property */,
160 v8::Local<v8::Value> value,
161 const v8::AccessorInfo& info)
163 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
164 if (!r || r->type != QV8SqlDatabaseResource::Rows)
165 V8THROW_REFERENCE_VOID("Not a SQLDatabase::Rows object");
167 r->query.setForwardOnly(value->BooleanValue());
170 QQmlSqlDatabaseData::~QQmlSqlDatabaseData()
172 qPersistentDispose(constructor);
173 qPersistentDispose(queryConstructor);
174 qPersistentDispose(rowsConstructor);
177 static QString qmlsqldatabase_databasesPath(QV8Engine *engine)
179 return engine->engine()->offlineStoragePath() +
180 QDir::separator() + QLatin1String("Databases");
183 static void qmlsqldatabase_initDatabasesPath(QV8Engine *engine)
185 QDir().mkpath(qmlsqldatabase_databasesPath(engine));
188 static QString qmlsqldatabase_databaseFile(const QString& connectionName, QV8Engine *engine)
190 return qmlsqldatabase_databasesPath(engine) + QDir::separator() + connectionName;
193 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(QV8SqlDatabaseResource *r, uint32_t index)
195 if (r->query.at() == (int)index || r->query.seek(index)) {
197 QSqlRecord record = r->query.record();
199 v8::Local<v8::Object> row = v8::Object::New();
200 for (int ii = 0; ii < record.count(); ++ii) {
201 QVariant v = record.value(ii);
203 row->Set(r->engine->toString(record.fieldName(ii)), v8::Null());
205 row->Set(r->engine->toString(record.fieldName(ii)),
206 r->engine->fromVariant(v));
211 return v8::Undefined();
215 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(uint32_t index, const v8::AccessorInfo& info)
217 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
218 if (!r || r->type != QV8SqlDatabaseResource::Rows)
219 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
221 return qmlsqldatabase_rows_index(r, index);
224 static v8::Handle<v8::Value> qmlsqldatabase_rows_item(const v8::Arguments& args)
226 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
227 if (!r || r->type != QV8SqlDatabaseResource::Rows)
228 V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
230 return qmlsqldatabase_rows_index(r, args.Length()?args[0]->Uint32Value():0);
233 static v8::Handle<v8::Value> qmlsqldatabase_executeSql(const v8::Arguments& args)
235 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
236 if (!r || r->type != QV8SqlDatabaseResource::Query)
237 V8THROW_REFERENCE("Not a SQLDatabase::Query object");
239 QV8Engine *engine = r->engine;
241 if (!r->inTransaction)
242 V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,QQmlEngine::tr("executeSql called outside transaction()"));
244 QSqlDatabase db = r->database;
246 QString sql = engine->toString(args[0]);
248 if (r->readonly && !sql.startsWith(QLatin1String("SELECT"),Qt::CaseInsensitive)) {
249 V8THROW_SQL(SQLEXCEPTION_SYNTAX_ERR, QQmlEngine::tr("Read-only Transaction"));
255 v8::Handle<v8::Value> result = v8::Undefined();
257 if (query.prepare(sql)) {
258 if (args.Length() > 1) {
259 v8::Local<v8::Value> values = args[1];
260 if (values->IsArray()) {
261 v8::Local<v8::Array> array = v8::Local<v8::Array>::Cast(values);
262 uint32_t size = array->Length();
263 for (uint32_t ii = 0; ii < size; ++ii)
264 query.bindValue(ii, engine->toVariant(array->Get(ii), -1));
265 } else if (values->IsObject() && !values->ToObject()->GetExternalResource()) {
266 v8::Local<v8::Object> object = values->ToObject();
267 v8::Local<v8::Array> names = object->GetPropertyNames();
268 uint32_t size = names->Length();
269 for (uint32_t ii = 0; ii < size; ++ii)
270 query.bindValue(engine->toString(names->Get(ii)),
271 engine->toVariant(object->Get(names->Get(ii)), -1));
273 query.bindValue(0, engine->toVariant(values, -1));
277 v8::Handle<v8::Object> rows = databaseData(engine)->rowsConstructor->NewInstance();
278 QV8SqlDatabaseResource *r = new QV8SqlDatabaseResource(engine);
279 r->type = QV8SqlDatabaseResource::Rows;
282 rows->SetExternalResource(r);
284 v8::Local<v8::Object> resultObject = v8::Object::New();
285 result = resultObject;
287 resultObject->Set(v8::String::New("rowsAffected"), v8::Integer::New(query.numRowsAffected()));
288 resultObject->Set(v8::String::New("insertId"), engine->toString(query.lastInsertId().toString()));
289 resultObject->Set(v8::String::New("rows"), rows);
297 V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,query.lastError().text());
302 static v8::Handle<v8::Value> qmlsqldatabase_changeVersion(const v8::Arguments& args)
304 if (args.Length() < 2)
305 return v8::Undefined();
307 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
308 if (!r || r->type != QV8SqlDatabaseResource::Database)
309 V8THROW_REFERENCE("Not a SQLDatabase object");
311 QV8Engine *engine = r->engine;
313 QSqlDatabase db = r->database;
314 QString from_version = engine->toString(args[0]);
315 QString to_version = engine->toString(args[1]);
316 v8::Handle<v8::Value> callback = args[2];
318 if (from_version != r->version)
319 V8THROW_SQL(SQLEXCEPTION_VERSION_ERR, QQmlEngine::tr("Version mismatch: expected %1, found %2").arg(from_version).arg(r->version));
321 v8::Local<v8::Object> instance = databaseData(engine)->queryConstructor->NewInstance();
322 QV8SqlDatabaseResource *r2 = new QV8SqlDatabaseResource(engine);
323 r2->type = QV8SqlDatabaseResource::Query;
325 r2->version = r->version;
326 r2->inTransaction = true;
327 instance->SetExternalResource(r2);
330 if (callback->IsFunction()) {
335 v8::Handle<v8::Value> callbackArgs[] = { instance };
336 v8::Handle<v8::Function>::Cast(callback)->Call(engine->global(), 1, callbackArgs);
338 if (tc.HasCaught()) {
341 return v8::Handle<v8::Value>();
342 } else if (!db.commit()) {
344 V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QQmlEngine::tr("SQL transaction failed"));
350 r2->inTransaction = false;
353 r2->version = to_version;
354 #ifndef QT_NO_SETTINGS
355 QSettings ini(qmlsqldatabase_databaseFile(db.connectionName(),engine) + QLatin1String(".ini"), QSettings::IniFormat);
356 ini.setValue(QLatin1String("Version"), to_version);
360 return v8::Undefined();
363 static v8::Handle<v8::Value> qmlsqldatabase_transaction_shared(const v8::Arguments& args, bool readOnly)
365 QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
366 if (!r || r->type != QV8SqlDatabaseResource::Database)
367 V8THROW_REFERENCE("Not a SQLDatabase object");
369 QV8Engine *engine = r->engine;
371 if (args.Length() == 0 || !args[0]->IsFunction())
372 V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QQmlEngine::tr("transaction: missing callback"));
374 QSqlDatabase db = r->database;
375 v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(args[0]);
377 v8::Local<v8::Object> instance = databaseData(engine)->queryConstructor->NewInstance();
378 QV8SqlDatabaseResource *q = new QV8SqlDatabaseResource(engine);
379 q->type = QV8SqlDatabaseResource::Query;
381 q->readonly = readOnly;
382 q->inTransaction = true;
383 instance->SetExternalResource(q);
387 v8::Handle<v8::Value> callbackArgs[] = { instance };
388 callback->Call(engine->global(), 1, callbackArgs);
390 q->inTransaction = false;
392 if (tc.HasCaught()) {
395 return v8::Handle<v8::Value>();
396 } else if (!db.commit()) {
400 return v8::Undefined();
403 static v8::Handle<v8::Value> qmlsqldatabase_transaction(const v8::Arguments& args)
405 return qmlsqldatabase_transaction_shared(args, false);
408 static v8::Handle<v8::Value> qmlsqldatabase_read_transaction(const v8::Arguments& args)
410 return qmlsqldatabase_transaction_shared(args, true);
413 QQmlSqlDatabaseData::QQmlSqlDatabaseData(QV8Engine *engine)
416 v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
417 ft->InstanceTemplate()->SetHasExternalResource(true);
418 ft->PrototypeTemplate()->Set(v8::String::New("transaction"),
419 V8FUNCTION(qmlsqldatabase_transaction, engine));
420 ft->PrototypeTemplate()->Set(v8::String::New("readTransaction"),
421 V8FUNCTION(qmlsqldatabase_read_transaction, engine));
422 ft->PrototypeTemplate()->SetAccessor(v8::String::New("version"), qmlsqldatabase_version);
423 ft->PrototypeTemplate()->Set(v8::String::New("changeVersion"),
424 V8FUNCTION(qmlsqldatabase_changeVersion, engine));
425 constructor = qPersistentNew<v8::Function>(ft->GetFunction());
429 v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
430 ft->InstanceTemplate()->SetHasExternalResource(true);
431 ft->PrototypeTemplate()->Set(v8::String::New("executeSql"),
432 V8FUNCTION(qmlsqldatabase_executeSql, engine));
433 queryConstructor = qPersistentNew<v8::Function>(ft->GetFunction());
436 v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
437 ft->InstanceTemplate()->SetHasExternalResource(true);
438 ft->PrototypeTemplate()->Set(v8::String::New("item"), V8FUNCTION(qmlsqldatabase_rows_item, engine));
439 ft->PrototypeTemplate()->SetAccessor(v8::String::New("length"), qmlsqldatabase_rows_length);
440 ft->InstanceTemplate()->SetAccessor(v8::String::New("forwardOnly"), qmlsqldatabase_rows_forwardOnly,
441 qmlsqldatabase_rows_setForwardOnly);
442 ft->InstanceTemplate()->SetIndexedPropertyHandler(qmlsqldatabase_rows_index);
443 rowsConstructor = qPersistentNew<v8::Function>(ft->GetFunction());
448 HTML5 "spec" says "rs.rows[n]", but WebKit only impelments "rs.rows.item(n)". We do both (and property iterator).
449 We add a "forwardOnly" property that stops Qt caching results (code promises to only go forward
455 \qmlclass LocalStorage QQuickLocalStorage
456 \inqmlmodule QtQuick.LocalStorage 2
459 \brief The LocalStorage module API provides the ability to access local offline storage in an SQL database.
461 These databases are user-specific and QML-specific, but accessible to all QML applications.
462 They are stored in the \c Databases subdirectory
463 of QQmlEngine::offlineStoragePath(), currently as SQLite databases.
465 Database connections are automatically closed during Javascript garbage collection.
467 The API can be used from JavaScript functions in your QML:
469 \snippet declarative/sqllocalstorage/hello.qml 0
471 The API conforms to the Synchronous API of the HTML5 Web Database API,
472 \link http://www.w3.org/TR/2009/WD-webdatabase-20091029/ W3C Working Draft 29 October 2009\endlink.
474 The \l{declarative/sqllocalstorage}{SQL Local Storage example} demonstrates the basics of
475 using the Offline Storage API.
477 \section3 Open or create a databaseData
479 import QtQuick.LocalStorage 2.0 as LS
481 db = LS.openDatabaseSync(identifier, version, description, estimated_size, callback(db))
483 The above code returns the database identified by \i identifier. If the database does not already exist, it
484 is created, and the function \i callback is called with the database as a parameter. \i description
485 and \i estimated_size are written to the INI file (described below), but are otherwise currently
488 May throw exception with code property SQLException.DATABASE_ERR, or SQLException.VERSION_ERR.
490 When a database is first created, an INI file is also created specifying its characteristics:
493 \header \o \bold {Key} \o \bold {Value}
494 \row \o Name \o The name of the database passed to \c openDatabase()
495 \row \o Version \o The version of the database passed to \c openDatabase()
496 \row \o Description \o The description of the database passed to \c openDatabase()
497 \row \o EstimatedSize \o The estimated size (in bytes) of the database passed to \c openDatabase()
498 \row \o Driver \o Currently "QSQLITE"
501 This data can be used by application tools.
503 \section3 db.changeVersion(from, to, callback(tx))
505 This method allows you to perform a \i{Scheme Upgrade}.
507 If the current version of \i db is not \i from, then an exception is thrown.
509 Otherwise, a database transaction is created and passed to \i callback. In this function,
510 you can call \i executeSql on \i tx to upgrade the database.
512 May throw exception with code property SQLException.DATABASE_ERR or SQLException.UNKNOWN_ERR.
514 \section3 db.transaction(callback(tx))
516 This method creates a read/write transaction and passed to \i callback. In this function,
517 you can call \i executeSql on \i tx to read and modify the database.
519 If the callback throws exceptions, the transaction is rolled back.
521 \section3 db.readTransaction(callback(tx))
523 This method creates a read-only transaction and passed to \i callback. In this function,
524 you can call \i executeSql on \i tx to read the database (with SELECT statements).
526 \section3 results = tx.executeSql(statement, values)
528 This method executes a SQL \i statement, binding the list of \i values to SQL positional parameters ("?").
530 It returns a results object, with the following properties:
533 \header \o \bold {Type} \o \bold {Property} \o \bold {Value} \o \bold {Applicability}
534 \row \o int \o rows.length \o The number of rows in the result \o SELECT
535 \row \o var \o rows.item(i) \o Function that returns row \i i of the result \o SELECT
536 \row \o int \o rowsAffected \o The number of rows affected by a modification \o UPDATE, DELETE
537 \row \o string \o insertId \o The id of the row inserted \o INSERT
540 May throw exception with code property SQLException.DATABASE_ERR, SQLException.SYNTAX_ERR, or SQLException.UNKNOWN_ERR.
542 class QQuickLocalStorage : public QObject
546 QQuickLocalStorage(QObject *parent=0) : QObject(parent)
549 ~QQuickLocalStorage() {
552 Q_INVOKABLE void openDatabaseSync(QQmlV8Function* args);
556 * \qmlmethod object LocalStorage::openDatabaseSync(string name, string version, string description, int estimated_size, jsobject callback(db))
557 * \brief Open or create a local storage sql database by given parameters.
559 * \c name is the database name
560 * \c version is the database version
561 * \c description is the database display name
562 * \c estimated_size is the database's estimated size, in bytes
563 * \c callback is an optional parameter, which is invoked if the database has not yet been created.
564 * \return the database object
566 void QQuickLocalStorage::openDatabaseSync(QQmlV8Function *args)
568 #ifndef QT_NO_SETTINGS
569 QV8Engine *engine = args->engine();
570 qmlsqldatabase_initDatabasesPath(engine);
572 QSqlDatabase database;
574 QString dbname = engine->toString((*args)[0]);
575 QString dbversion = engine->toString((*args)[1]);
576 QString dbdescription = engine->toString((*args)[2]);
577 int dbestimatedsize = (*args)[3]->Int32Value();
578 v8::Handle<v8::Value> dbcreationCallback = (*args)[4];
580 QCryptographicHash md5(QCryptographicHash::Md5);
581 md5.addData(dbname.toUtf8());
582 QString dbid(QLatin1String(md5.result().toHex()));
584 QString basename = qmlsqldatabase_databaseFile(dbid, engine);
585 bool created = false;
586 QString version = dbversion;
589 QSettings ini(basename+QLatin1String(".ini"),QSettings::IniFormat);
591 if (QSqlDatabase::connectionNames().contains(dbid)) {
592 database = QSqlDatabase::database(dbid);
593 version = ini.value(QLatin1String("Version")).toString();
594 if (version != dbversion && !dbversion.isEmpty() && !version.isEmpty())
595 V8THROW_SQL_VOID(SQLEXCEPTION_VERSION_ERR, QQmlEngine::tr("SQL: database version mismatch"));
597 created = !QFile::exists(basename+QLatin1String(".sqlite"));
598 database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid);
600 ini.setValue(QLatin1String("Name"), dbname);
601 if (dbcreationCallback->IsFunction())
603 ini.setValue(QLatin1String("Version"), version);
604 ini.setValue(QLatin1String("Description"), dbdescription);
605 ini.setValue(QLatin1String("EstimatedSize"), dbestimatedsize);
606 ini.setValue(QLatin1String("Driver"), QLatin1String("QSQLITE"));
608 if (!dbversion.isEmpty() && ini.value(QLatin1String("Version")) != dbversion) {
610 V8THROW_SQL_VOID(SQLEXCEPTION_VERSION_ERR,QQmlEngine::tr("SQL: database version mismatch"));
612 version = ini.value(QLatin1String("Version")).toString();
614 database.setDatabaseName(basename+QLatin1String(".sqlite"));
616 if (!database.isOpen())
620 v8::Local<v8::Object> instance = databaseData(engine)->constructor->NewInstance();
622 QV8SqlDatabaseResource *r = new QV8SqlDatabaseResource(engine);
623 r->database = database;
624 r->version = version;
625 instance->SetExternalResource(r);
627 if (created && dbcreationCallback->IsFunction()) {
629 v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(dbcreationCallback);
630 v8::Handle<v8::Value> args[] = { instance };
631 callback->Call(engine->global(), 1, args);
632 if (tc.HasCaught()) {
638 args->returnValue(instance);
639 #endif // QT_NO_SETTINGS
642 static QObject *module_api_factory(QQmlEngine *engine, QJSEngine *scriptEngine)
645 Q_UNUSED(scriptEngine)
646 QQuickLocalStorage *api = new QQuickLocalStorage();
651 class QQmlLocalStoragePlugin : public QQmlExtensionPlugin
654 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface" FILE "localstorage.json")
657 QQmlLocalStoragePlugin()
661 void registerTypes(const char *uri)
663 Q_ASSERT(QLatin1String(uri) == "QtQuick.LocalStorage");
664 qmlRegisterModuleApi(uri, 2, 0, module_api_factory);
668 #include "plugin.moc"