Update to 5.0.0-beta1
[profile/ivi/qtdeclarative.git] / src / imports / localstorage / plugin.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the test suite of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 #include <QStringList>
42 #include <QtQml/qqmlextensionplugin.h>
43 #include <QtQml/qqml.h>
44 #include <private/qqmlengine_p.h>
45 #include <QDebug>
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>
58
59
60 #define V8THROW_SQL(error, desc) \
61 { \
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>(); \
66 }
67
68 #define V8THROW_SQL_VOID(error, desc) \
69 { \
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); \
73     return; \
74 }
75
76 #define V8THROW_REFERENCE(string) { \
77     v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
78     return v8::Handle<v8::Value>(); \
79 }
80
81 #define V8THROW_REFERENCE_VOID(string) { \
82     v8::ThrowException(v8::Exception::ReferenceError(v8::String::New(string))); \
83     return; \
84 }
85
86 class QQmlSqlDatabaseData : public QV8Engine::Deletable
87 {
88 public:
89     QQmlSqlDatabaseData(QV8Engine *engine);
90     ~QQmlSqlDatabaseData();
91
92     v8::Persistent<v8::Function> constructor;
93     v8::Persistent<v8::Function> queryConstructor;
94     v8::Persistent<v8::Function> rowsConstructor;
95 };
96
97 V8_DEFINE_EXTENSION(QQmlSqlDatabaseData, databaseData)
98
99 class QV8SqlDatabaseResource : public QV8ObjectResource
100 {
101     V8_RESOURCE_TYPE(SQLDatabaseType)
102
103 public:
104     enum Type { Database, Query, Rows };
105     QV8SqlDatabaseResource(QV8Engine *e)
106     : QV8ObjectResource(e), type(Database), inTransaction(false), readonly(false), forwardOnly(false) {}
107
108     ~QV8SqlDatabaseResource() {
109     }
110
111     Type type;
112     QSqlDatabase database;
113
114     QString version; // type == Database
115
116     bool inTransaction; // type == Query
117     bool readonly;   // type == Query
118
119     QSqlQuery query; // type == Rows
120     bool forwardOnly; // type == Rows
121 };
122
123 static v8::Handle<v8::Value> qmlsqldatabase_version(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
124 {
125     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
126     if (!r || r->type != QV8SqlDatabaseResource::Database)
127         V8THROW_REFERENCE("Not a SQLDatabase object");
128
129     return r->engine->toString(r->version);
130 }
131
132 static v8::Handle<v8::Value> qmlsqldatabase_rows_length(v8::Local<v8::String> /* property */, const v8::AccessorInfo& info)
133 {
134     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
135     if (!r || r->type != QV8SqlDatabaseResource::Rows)
136         V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
137
138     int s = r->query.size();
139     if (s < 0) {
140         // Inefficient
141         if (r->query.last()) {
142             s = r->query.at() + 1;
143         } else {
144             s = 0;
145         }
146     }
147     return v8::Integer::New(s);
148 }
149
150 static v8::Handle<v8::Value> qmlsqldatabase_rows_forwardOnly(v8::Local<v8::String> /* property */,
151                                                              const v8::AccessorInfo& info)
152 {
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());
157 }
158
159 static void qmlsqldatabase_rows_setForwardOnly(v8::Local<v8::String> /* property */,
160                                                v8::Local<v8::Value> value,
161                                                const v8::AccessorInfo& info)
162 {
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");
166
167     r->query.setForwardOnly(value->BooleanValue());
168 }
169
170 QQmlSqlDatabaseData::~QQmlSqlDatabaseData()
171 {
172     qPersistentDispose(constructor);
173     qPersistentDispose(queryConstructor);
174     qPersistentDispose(rowsConstructor);
175 }
176
177 static QString qmlsqldatabase_databasesPath(QV8Engine *engine)
178 {
179     return engine->engine()->offlineStoragePath() +
180            QDir::separator() + QLatin1String("Databases");
181 }
182
183 static void qmlsqldatabase_initDatabasesPath(QV8Engine *engine)
184 {
185     QDir().mkpath(qmlsqldatabase_databasesPath(engine));
186 }
187
188 static QString qmlsqldatabase_databaseFile(const QString& connectionName, QV8Engine *engine)
189 {
190     return qmlsqldatabase_databasesPath(engine) + QDir::separator() + connectionName;
191 }
192
193 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(QV8SqlDatabaseResource *r, uint32_t index)
194 {
195     if (r->query.at() == (int)index || r->query.seek(index)) {
196
197         QSqlRecord record = r->query.record();
198         // XXX optimize
199         v8::Local<v8::Object> row = v8::Object::New();
200         for (int ii = 0; ii < record.count(); ++ii) {
201             QVariant v = record.value(ii);
202             if (v.isNull()) {
203                 row->Set(r->engine->toString(record.fieldName(ii)), v8::Null());
204             } else {
205                 row->Set(r->engine->toString(record.fieldName(ii)),
206                          r->engine->fromVariant(v));
207             }
208         }
209         return row;
210     } else {
211         return v8::Undefined();
212     }
213 }
214
215 static v8::Handle<v8::Value> qmlsqldatabase_rows_index(uint32_t index, const v8::AccessorInfo& info)
216 {
217     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(info.This());
218     if (!r || r->type != QV8SqlDatabaseResource::Rows)
219         V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
220
221     return qmlsqldatabase_rows_index(r, index);
222 }
223
224 static v8::Handle<v8::Value> qmlsqldatabase_rows_item(const v8::Arguments& args)
225 {
226     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
227     if (!r || r->type != QV8SqlDatabaseResource::Rows)
228         V8THROW_REFERENCE("Not a SQLDatabase::Rows object");
229
230     return qmlsqldatabase_rows_index(r, args.Length()?args[0]->Uint32Value():0);
231 }
232
233 static v8::Handle<v8::Value> qmlsqldatabase_executeSql(const v8::Arguments& args)
234 {
235     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
236     if (!r || r->type != QV8SqlDatabaseResource::Query)
237         V8THROW_REFERENCE("Not a SQLDatabase::Query object");
238
239     QV8Engine *engine = r->engine;
240
241     if (!r->inTransaction)
242         V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,QQmlEngine::tr("executeSql called outside transaction()"));
243
244     QSqlDatabase db = r->database;
245
246     QString sql = engine->toString(args[0]);
247
248     if (r->readonly && !sql.startsWith(QLatin1String("SELECT"),Qt::CaseInsensitive)) {
249         V8THROW_SQL(SQLEXCEPTION_SYNTAX_ERR, QQmlEngine::tr("Read-only Transaction"));
250     }
251
252     QSqlQuery query(db);
253     bool err = false;
254
255     v8::Handle<v8::Value> result = v8::Undefined();
256
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));
272             } else {
273                 query.bindValue(0, engine->toVariant(values, -1));
274             }
275         }
276         if (query.exec()) {
277             v8::Handle<v8::Object> rows = databaseData(engine)->rowsConstructor->NewInstance();
278             QV8SqlDatabaseResource *r = new QV8SqlDatabaseResource(engine);
279             r->type = QV8SqlDatabaseResource::Rows;
280             r->database = db;
281             r->query = query;
282             rows->SetExternalResource(r);
283
284             v8::Local<v8::Object> resultObject = v8::Object::New();
285             result = resultObject;
286             // XXX optimize
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);
290         } else {
291             err = true;
292         }
293     } else {
294         err = true;
295     }
296     if (err)
297         V8THROW_SQL(SQLEXCEPTION_DATABASE_ERR,query.lastError().text());
298
299     return result;
300 }
301
302 static v8::Handle<v8::Value> qmlsqldatabase_changeVersion(const v8::Arguments& args)
303 {
304     if (args.Length() < 2)
305         return v8::Undefined();
306
307     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
308     if (!r || r->type != QV8SqlDatabaseResource::Database)
309         V8THROW_REFERENCE("Not a SQLDatabase object");
310
311     QV8Engine *engine = r->engine;
312
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];
317
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));
320
321     v8::Local<v8::Object> instance = databaseData(engine)->queryConstructor->NewInstance();
322     QV8SqlDatabaseResource *r2 = new QV8SqlDatabaseResource(engine);
323     r2->type = QV8SqlDatabaseResource::Query;
324     r2->database = db;
325     r2->version = r->version;
326     r2->inTransaction = true;
327     instance->SetExternalResource(r2);
328
329     bool ok = true;
330     if (callback->IsFunction()) {
331         ok = false;
332         db.transaction();
333
334         v8::TryCatch tc;
335         v8::Handle<v8::Value> callbackArgs[] = { instance };
336         v8::Handle<v8::Function>::Cast(callback)->Call(engine->global(), 1, callbackArgs);
337
338         if (tc.HasCaught()) {
339             db.rollback();
340             tc.ReThrow();
341             return v8::Handle<v8::Value>();
342         } else if (!db.commit()) {
343             db.rollback();
344             V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QQmlEngine::tr("SQL transaction failed"));
345         } else {
346             ok = true;
347         }
348     }
349
350     r2->inTransaction = false;
351
352     if (ok) {
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);
357 #endif
358     }
359
360     return v8::Undefined();
361 }
362
363 static v8::Handle<v8::Value> qmlsqldatabase_transaction_shared(const v8::Arguments& args, bool readOnly)
364 {
365     QV8SqlDatabaseResource *r = v8_resource_cast<QV8SqlDatabaseResource>(args.This());
366     if (!r || r->type != QV8SqlDatabaseResource::Database)
367         V8THROW_REFERENCE("Not a SQLDatabase object");
368
369     QV8Engine *engine = r->engine;
370
371     if (args.Length() == 0 || !args[0]->IsFunction())
372         V8THROW_SQL(SQLEXCEPTION_UNKNOWN_ERR,QQmlEngine::tr("transaction: missing callback"));
373
374     QSqlDatabase db = r->database;
375     v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(args[0]);
376
377     v8::Local<v8::Object> instance = databaseData(engine)->queryConstructor->NewInstance();
378     QV8SqlDatabaseResource *q = new QV8SqlDatabaseResource(engine);
379     q->type = QV8SqlDatabaseResource::Query;
380     q->database = db;
381     q->readonly = readOnly;
382     q->inTransaction = true;
383     instance->SetExternalResource(q);
384
385     db.transaction();
386     v8::TryCatch tc;
387     v8::Handle<v8::Value> callbackArgs[] = { instance };
388     callback->Call(engine->global(), 1, callbackArgs);
389
390     q->inTransaction = false;
391
392     if (tc.HasCaught()) {
393         db.rollback();
394         tc.ReThrow();
395         return v8::Handle<v8::Value>();
396     } else if (!db.commit()) {
397         db.rollback();
398     }
399
400     return v8::Undefined();
401 }
402
403 static v8::Handle<v8::Value> qmlsqldatabase_transaction(const v8::Arguments& args)
404 {
405     return qmlsqldatabase_transaction_shared(args, false);
406 }
407
408 static v8::Handle<v8::Value> qmlsqldatabase_read_transaction(const v8::Arguments& args)
409 {
410     return qmlsqldatabase_transaction_shared(args, true);
411 }
412
413 QQmlSqlDatabaseData::QQmlSqlDatabaseData(QV8Engine *engine)
414 {
415     {
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());
426     }
427
428     {
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());
434     }
435     {
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());
444     }
445 }
446
447 /*
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
450 through the data.
451 */
452
453
454 /*!
455     \qmlmodule QtQuick.LocalStorage 2
456     \title QML Module QtQuick.LocalStorage 2.0
457     \brief Provides a JavaScript object singleton type for accessing a local SQLite database
458
459     This is a singleton type for reading and writing to SQLite databases.
460
461
462     \section1 Methods
463
464     \list
465     \li object \b{\l{#openDatabaseSync}{openDatabaseSync}}(string name, string version, string description, int estimated_size, jsobject callback(db))
466     \endlist
467
468
469     \section1 Detailed Description
470
471     To use the types in this module, import the module and call the
472     relevant functions using the \c LocalStorage type:
473
474     \code
475     import QtQuick.LocalStorage 2.0
476     import QtQuick 2.0
477
478     Item {
479         Component.onCompleted: {
480             var db = LocalStorage.openDatabaseSync(...)
481         }
482     }
483     \endcode
484
485
486 These databases are user-specific and QML-specific, but accessible to all QML applications.
487 They are stored in the \c Databases subdirectory
488 of QQmlEngine::offlineStoragePath(), currently as SQLite databases.
489
490 Database connections are automatically closed during Javascript garbage collection.
491
492 The API can be used from JavaScript functions in your QML:
493
494 \snippet localstorage/localstorage/hello.qml 0
495
496 The API conforms to the Synchronous API of the HTML5 Web Database API,
497 \link http://www.w3.org/TR/2009/WD-webdatabase-20091029/ W3C Working Draft 29 October 2009\endlink.
498
499 The \l{localstorage/localstorage}{SQL Local Storage example} demonstrates the basics of
500 using the Offline Storage API.
501
502 \section3 Open or create a databaseData
503 \code
504 import QtQuick.LocalStorage 2.0 as LS
505
506 db = Sql.openDatabaseSync(identifier, version, description, estimated_size, callback(db))
507 \endcode
508 The above code returns the database identified by \e identifier. If the database does not already exist, it
509 is created, and the function \e callback is called with the database as a parameter. \e description
510 and \e estimated_size are written to the INI file (described below), but are otherwise currently
511 unused.
512
513 May throw exception with code property SQLException.DATABASE_ERR, or SQLException.VERSION_ERR.
514
515 When a database is first created, an INI file is also created specifying its characteristics:
516
517 \table
518 \header \li \b {Key} \li \b {Value}
519 \row \li Name \li The name of the database passed to \c openDatabase()
520 \row \li Version \li The version of the database passed to \c openDatabase()
521 \row \li Description \li The description of the database passed to \c openDatabase()
522 \row \li EstimatedSize \li The estimated size (in bytes) of the database passed to \c openDatabase()
523 \row \li Driver \li Currently "QSQLITE"
524 \endtable
525
526 This data can be used by application tools.
527
528 \section3 db.changeVersion(from, to, callback(tx))
529
530 This method allows you to perform a \e{Scheme Upgrade}.
531
532 If the current version of \e db is not \e from, then an exception is thrown.
533
534 Otherwise, a database transaction is created and passed to \e callback. In this function,
535 you can call \e executeSql on \e tx to upgrade the database.
536
537 May throw exception with code property SQLException.DATABASE_ERR or SQLException.UNKNOWN_ERR.
538
539 \section3 db.transaction(callback(tx))
540
541 This method creates a read/write transaction and passed to \e callback. In this function,
542 you can call \e executeSql on \e tx to read and modify the database.
543
544 If the callback throws exceptions, the transaction is rolled back.
545
546 \section3 db.readTransaction(callback(tx))
547
548 This method creates a read-only transaction and passed to \e callback. In this function,
549 you can call \e executeSql on \e tx to read the database (with SELECT statements).
550
551 \section3 results = tx.executeSql(statement, values)
552
553 This method executes a SQL \e statement, binding the list of \e values to SQL positional parameters ("?").
554
555 It returns a results object, with the following properties:
556
557 \table
558 \header \li \b {Type} \li \b {Property} \li \b {Value} \li \b {Applicability}
559 \row \li int \li rows.length \li The number of rows in the result \li SELECT
560 \row \li var \li rows.item(i) \li Function that returns row \e i of the result \li SELECT
561 \row \li int \li rowsAffected \li The number of rows affected by a modification \li UPDATE, DELETE
562 \row \li string \li insertId \li The id of the row inserted \li INSERT
563 \endtable
564
565 May throw exception with code property SQLException.DATABASE_ERR, SQLException.SYNTAX_ERR, or SQLException.UNKNOWN_ERR.
566
567
568 \section1 Method Documentation
569
570 \target openDatabaseSync
571 \code
572 object openDatabaseSync(string name, string version, string description, int estimated_size, jsobject callback(db))
573 \endcode
574
575 Opens or creates a local storage sql database by the given parameters.
576
577 \list
578 \li \c name is the database name
579 \li \c version is the database version
580 \li \c description is the database display name
581 \li \c estimated_size is the database's estimated size, in bytes
582 \li \c callback is an optional parameter, which is invoked if the database has not yet been created.
583 \endlist
584
585 Returns the created database object.
586
587 */
588 class QQuickLocalStorage : public QObject
589 {
590     Q_OBJECT
591 public:
592     QQuickLocalStorage(QObject *parent=0) : QObject(parent)
593     {
594     }
595     ~QQuickLocalStorage() {
596     }
597
598    Q_INVOKABLE void openDatabaseSync(QQmlV8Function* args);
599 };
600
601 void QQuickLocalStorage::openDatabaseSync(QQmlV8Function *args)
602 {
603 #ifndef QT_NO_SETTINGS
604     QV8Engine *engine = args->engine();
605     if (engine->engine()->offlineStoragePath().isEmpty())
606         V8THROW_SQL_VOID(SQLEXCEPTION_DATABASE_ERR, QQmlEngine::tr("SQL: can't create database, offline storage is disabled."));
607
608     qmlsqldatabase_initDatabasesPath(engine);
609
610     QSqlDatabase database;
611
612     QString dbname = engine->toString((*args)[0]);
613     QString dbversion = engine->toString((*args)[1]);
614     QString dbdescription = engine->toString((*args)[2]);
615     int dbestimatedsize = (*args)[3]->Int32Value();
616     v8::Handle<v8::Value> dbcreationCallback = (*args)[4];
617
618     QCryptographicHash md5(QCryptographicHash::Md5);
619     md5.addData(dbname.toUtf8());
620     QString dbid(QLatin1String(md5.result().toHex()));
621
622     QString basename = qmlsqldatabase_databaseFile(dbid, engine);
623     bool created = false;
624     QString version = dbversion;
625
626     {
627         QSettings ini(basename+QLatin1String(".ini"),QSettings::IniFormat);
628
629         if (QSqlDatabase::connectionNames().contains(dbid)) {
630             database = QSqlDatabase::database(dbid);
631             version = ini.value(QLatin1String("Version")).toString();
632             if (version != dbversion && !dbversion.isEmpty() && !version.isEmpty())
633                 V8THROW_SQL_VOID(SQLEXCEPTION_VERSION_ERR, QQmlEngine::tr("SQL: database version mismatch"));
634         } else {
635             created = !QFile::exists(basename+QLatin1String(".sqlite"));
636             database = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), dbid);
637             if (created) {
638                 ini.setValue(QLatin1String("Name"), dbname);
639                 if (dbcreationCallback->IsFunction())
640                     version = QString();
641                 ini.setValue(QLatin1String("Version"), version);
642                 ini.setValue(QLatin1String("Description"), dbdescription);
643                 ini.setValue(QLatin1String("EstimatedSize"), dbestimatedsize);
644                 ini.setValue(QLatin1String("Driver"), QLatin1String("QSQLITE"));
645             } else {
646                 if (!dbversion.isEmpty() && ini.value(QLatin1String("Version")) != dbversion) {
647                     // Incompatible
648                     V8THROW_SQL_VOID(SQLEXCEPTION_VERSION_ERR,QQmlEngine::tr("SQL: database version mismatch"));
649                 }
650                 version = ini.value(QLatin1String("Version")).toString();
651             }
652             database.setDatabaseName(basename+QLatin1String(".sqlite"));
653         }
654         if (!database.isOpen())
655             database.open();
656     }
657
658     v8::Local<v8::Object> instance = databaseData(engine)->constructor->NewInstance();
659
660     QV8SqlDatabaseResource *r = new QV8SqlDatabaseResource(engine);
661     r->database = database;
662     r->version = version;
663     instance->SetExternalResource(r);
664
665     if (created && dbcreationCallback->IsFunction()) {
666         v8::TryCatch tc;
667         v8::Handle<v8::Function> callback = v8::Handle<v8::Function>::Cast(dbcreationCallback);
668         v8::Handle<v8::Value> args[] = { instance };
669         callback->Call(engine->global(), 1, args);
670         if (tc.HasCaught()) {
671             tc.ReThrow();
672             return;
673         }
674     }
675
676     args->returnValue(instance);
677 #endif // QT_NO_SETTINGS
678 }
679
680 static QObject *module_api_factory(QQmlEngine *engine, QJSEngine *scriptEngine)
681 {
682    Q_UNUSED(engine)
683    Q_UNUSED(scriptEngine)
684    QQuickLocalStorage *api = new QQuickLocalStorage();
685
686    return api;
687 }
688
689 class QQmlLocalStoragePlugin : public QQmlExtensionPlugin
690 {
691     Q_OBJECT
692     Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
693
694 public:
695     QQmlLocalStoragePlugin()
696     {
697     }
698
699     void registerTypes(const char *uri)
700     {
701         Q_ASSERT(QLatin1String(uri) == "QtQuick.LocalStorage");
702         qmlRegisterSingletonType<QQuickLocalStorage>(uri, 2, 0, "LocalStorage", module_api_factory);
703     }
704 };
705
706 #include "plugin.moc"