diff options
author | Cristiano di Flora <cristiano.di-flora@nokia.com> | 2012-04-27 11:21:51 +0300 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-06-04 11:32:23 +0200 |
commit | e3fff955aba148efa24e268ce1723b3421ef8834 (patch) | |
tree | 71b8c759f1e196149cf6ec666e85be124732a71c | |
parent | 58f67c7a13052363c8683c2ac9019b5f39231da6 (diff) |
Add new private QJsonDbQueryModel class to client.
This change separates the cachinglistmodel logic from
the implementation of qml-specific aspects of the
jsondbcachinglistmodel.
The code is migrated into new private classes in client and
reused by the imports/jsondb models.
The new classes include functionality of the following
components previously embedded in the qml jsondb plugin:
- JsonDbCachingListModel
- modelutils
- modelcache
In the new setup, the QJsonDbQueryModel class
implements the functionality previously provided by the
src/imports/jsondb JsonDbCachingListModel and
JsonDbCachingListModelPrivate classes.
Modelutils and modelcache helpers are now also part of the
private API in client.
No functional / performance regression is observed
after running models tests and benchmarks on top of this.
The idea is to make QJsonDbQueryModel part of the public
C++ api after a few rounds of reviewes & iterations.
Change-Id: Ia6e2368a4cb7c7485087e714642e00ff686945b7
Reviewed-by: Tapani Mikola <tapani.mikola@nokia.com>
20 files changed, 2343 insertions, 1744 deletions
diff --git a/src/client/client.pro b/src/client/client.pro index 65fbb3a..187fb11 100644 --- a/src/client/client.pro +++ b/src/client/client.pro @@ -38,7 +38,12 @@ HEADERS += \ qjsondbprivatepartition_p.h \ qjsondbstandardpaths_p.h \ qjsondblogrequest_p.h \ - qjsondblogrequest_p_p.h + qjsondblogrequest_p_p.h \ + qjsondbquerymodel_p_p.h \ + qjsondbmodelcache_p.h \ + qjsondbmodelutils_p.h \ + qjsondbquerymodel_p.h + SOURCES += \ qjsondbconnection.cpp \ @@ -50,6 +55,9 @@ SOURCES += \ qjsondbobject.cpp \ qjsondbprivatepartition.cpp \ qjsondbstandardpaths.cpp \ - qjsondblogrequest.cpp + qjsondblogrequest.cpp \ + qjsondbmodelcache_p.cpp \ + qjsondbmodelutils_p.cpp \ + qjsondbquerymodel_p.cpp mac:QMAKE_FRAMEWORK_BUNDLE_NAME = $$QT.jsondb.name diff --git a/src/imports/jsondb/jsondbmodelcache.cpp b/src/client/qjsondbmodelcache_p.cpp index 4db1b1d..3b98e11 100644 --- a/src/imports/jsondb/jsondbmodelcache.cpp +++ b/src/client/qjsondbmodelcache_p.cpp @@ -40,7 +40,7 @@ ****************************************************************************/ //#define JSONDB_LISTMODEL_DEBUG -#include "jsondbmodelcache.h" +#include "qjsondbmodelcache_p.h" #include <QMap> #include <QDebug> diff --git a/src/imports/jsondb/jsondbmodelcache.h b/src/client/qjsondbmodelcache_p.h index 83ace7d..35e2b21 100644 --- a/src/imports/jsondb/jsondbmodelcache.h +++ b/src/client/qjsondbmodelcache_p.h @@ -40,21 +40,33 @@ ****************************************************************************/ -#ifndef JSONDBMODELCACHE_H -#define JSONDBMODELCACHE_H +#ifndef JSONDBMODELCACHE_P_H +#define JSONDBMODELCACHE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the QtJsonDb API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + #include <QHash> #include <QObject> #include <QList> #include <QJsonObject> -#include "jsondbmodelutils.h" +#include "qjsondbmodelutils_p.h" QT_BEGIN_NAMESPACE_JSONDB typedef QMap<SortingKey, QString> JsonDbModelIndexType; typedef QHash<QString, QJsonObject> JsonDbModelObjectType; -class ModelPage +class Q_JSONDB_EXPORT ModelPage { public: int index; @@ -75,7 +87,7 @@ public: void dumpPageDetails(); }; -class ModelCache +class Q_JSONDB_EXPORT ModelCache { public: static qulonglong currentCounter; @@ -116,4 +128,4 @@ public: QT_END_NAMESPACE_JSONDB -#endif // JSONDBMODELCACHE_H +#endif // JSONDBMODELCACHE_P_H diff --git a/src/imports/jsondb/jsondbmodelutils.cpp b/src/client/qjsondbmodelutils_p.cpp index 372aab8..d9003ab 100644 --- a/src/imports/jsondb/jsondbmodelutils.cpp +++ b/src/client/qjsondbmodelutils_p.cpp @@ -39,7 +39,7 @@ ** ****************************************************************************/ -#include "jsondbmodelutils.h" +#include "qjsondbmodelutils_p.h" #include <qdebug.h> #include <QJsonValue> #include <QJsonArray> @@ -82,6 +82,18 @@ int SortingKey::partitionIndex() const return d->partitionIndex; } +QVariant SortingKey::value() const +{ + if (d->count == 1) + return d->values[0]; + else if (d->count == 0) + return QVariant(); + QVariantList ret; + for (int i = 0; i < d->count; i++) + ret << d->values[i]; + return ret; +} + static bool operator<(const QVariant& lhs, const QVariant& rhs) { if ((lhs.type() == QVariant::Int) && (rhs.type() == QVariant::Int)) @@ -267,6 +279,25 @@ QString removeArrayOperator(QString propertyName) return propertyName; } +QList<QJsonObject> qvariantlist_to_qjsonobject_list(const QVariantList &list) +{ + QList<QJsonObject> objects; + int count = list.count(); + for (int i = 0; i < count; i++) { + objects.append(QJsonObject::fromVariantMap(list[i].toMap())); + } + return objects; +} + +QVariantList qjsonobject_list_to_qvariantlist(const QList<QJsonObject> &list) +{ + QVariantList objects; + int count = list.count(); + for (int i = 0; i < count; i++) { + objects.append(list[i].toVariantMap()); + } + return objects; +} ModelRequest::ModelRequest(QObject *parent) :QObject(parent) @@ -305,5 +336,5 @@ void ModelRequest::onQueryFinished() emit finished(index, request->takeResults(), request->sortKey()); } -#include "moc_jsondbmodelutils.cpp" +#include "moc_qjsondbmodelutils_p.cpp" QT_END_NAMESPACE_JSONDB diff --git a/src/imports/jsondb/jsondbmodelutils.h b/src/client/qjsondbmodelutils_p.h index 0d75b03..28427c2 100644 --- a/src/imports/jsondb/jsondbmodelutils.h +++ b/src/client/qjsondbmodelutils_p.h @@ -39,12 +39,25 @@ ** ****************************************************************************/ -#ifndef JSONDBMODELUTILS_H -#define JSONDBMODELUTILS_H + +#ifndef JSONDBMODELUTILS_P_H +#define JSONDBMODELUTILS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the QtJsonDb API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + + #include <QSharedData> #include <QStringList> #include <QUuid> -#include <QJSValue> #include <QVariant> #include <QPointer> #include <QJsonDbWatcher> @@ -52,12 +65,6 @@ QT_BEGIN_NAMESPACE_JSONDB -struct CallbackInfo { - int index; - QJSValue successCallback; - QJSValue errorCallback; -}; - struct NotificationItem { int partitionIndex; QJsonObject item; @@ -87,10 +94,9 @@ struct SortIndexSpec {} }; -class JsonDbListModelPrivate; -class ModelRequest : public QObject + +class Q_JSONDB_EXPORT ModelRequest : public QObject { - friend class JsonDbListModelPrivate; Q_OBJECT public: @@ -124,7 +130,7 @@ struct IndexInfo class SortingKeyPrivate; -class SortingKey { +class Q_JSONDB_EXPORT SortingKey { public: SortingKey(int partitionIndex, const QVariantMap &object, const QList<bool> &directions, const QList<QStringList> &paths, const SortIndexSpec &spec = SortIndexSpec()); SortingKey(int partitionIndex, const QVariantList &object, const QList<bool> &directions, const SortIndexSpec &spec = SortIndexSpec()); @@ -133,6 +139,7 @@ public: SortingKey(const SortingKey&); SortingKey() {} int partitionIndex() const; + QVariant value() const; bool operator <(const SortingKey &rhs) const; bool operator ==(const SortingKey &rhs) const; private: @@ -211,13 +218,12 @@ template <typename T> int iterator_position(T &begin, T &end, T &value) return i; } -QVariant lookupProperty(QVariantMap object, const QStringList &path); -QJsonValue lookupJsonProperty(QJsonObject object, const QStringList &path); -QString removeArrayOperator(QString propertyName); -QList<QJsonObject> qvariantlist_to_qjsonobject_list(const QVariantList &list); -QVariantList qjsonobject_list_to_qvariantlist(const QList<QJsonObject> &list); -QJSValue qjsonobject_list_to_qjsvalue(const QList<QJsonObject> &list); +Q_JSONDB_EXPORT QVariant lookupProperty(QVariantMap object, const QStringList &path); +Q_JSONDB_EXPORT QJsonValue lookupJsonProperty(QJsonObject object, const QStringList &path); +Q_JSONDB_EXPORT QString removeArrayOperator(QString propertyName); +Q_JSONDB_EXPORT QList<QJsonObject> qvariantlist_to_qjsonobject_list(const QVariantList &list); +Q_JSONDB_EXPORT QVariantList qjsonobject_list_to_qvariantlist(const QList<QJsonObject> &list); QT_END_NAMESPACE_JSONDB -#endif // JSONDBMODELUTILS_H +#endif // JSONDBMODELUTILS_P_H diff --git a/src/client/qjsondbquerymodel_p.cpp b/src/client/qjsondbquerymodel_p.cpp new file mode 100644 index 0000000..fddac44 --- /dev/null +++ b/src/client/qjsondbquerymodel_p.cpp @@ -0,0 +1,1794 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtAddOn.JsonDb module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//#define JSONDB_LISTMODEL_DEBUG +//#define JSONDB_LISTMODEL_BENCHMARK + +#include "qjsondbquerymodel_p.h" +#include "qjsondbquerymodel_p_p.h" +#include "qjsondbconnection.h" +#include <QDebug> +#ifdef JSONDB_LISTMODEL_BENCHMARK +#include <QElapsedTimer> +#endif + +QT_BEGIN_NAMESPACE_JSONDB + +QJsonDbQueryModelPrivate::QJsonDbQueryModelPrivate(QJsonDbQueryModel *q) + : q_ptr(q) + , componentComplete(false) + , resetModel(true) + , cacheSize(-1) + , state(QJsonDbQueryModel::None) + , errorCode(0) + , lastQueriedIndex(-1) +{ + setCacheParams(INT_MAX/10); +} + +void QJsonDbQueryModelPrivate::init(QJsonDbConnection *dbConnection) +{ + mConnection = dbConnection; +} + +void QJsonDbQueryModelPrivate::setCacheParams(int maxItems) +{ + objectCache.setPageSize(maxItems); + chunkSize = objectCache.chunkSize(); + lowWaterMark = objectCache.chunkSize()/4; +} + +QJsonDbQueryModelPrivate::~QJsonDbQueryModelPrivate() +{ + clearNotifications(); + while (!keyRequests.isEmpty()) { + delete keyRequests[0]; + keyRequests.removeFirst(); + } + while (!indexRequests.isEmpty()) { + delete indexRequests[0]; + indexRequests.removeFirst(); + } + while (!valueRequests.isEmpty()) { + delete valueRequests[0]; + valueRequests.removeFirst(); + } + +} + +// insert item notification handler +// + add items, for chunked read +void QJsonDbQueryModelPrivate::addItem(const QJsonObject &item, int partitionIndex) +{ + Q_Q(QJsonDbQueryModel); + const QString &uuid = item.value(QLatin1String("_uuid")).toString(); + // ignore duplicates. + if (objectSortValues.contains(uuid)) + return; + + QVariantList vl; + vl.append(uuid); + vl.append(item.value(QLatin1String("_indexValue")).toVariant()); + SortingKey key(partitionIndex, vl, QList<bool>() << ascendingOrder, partitionIndexDetails[partitionIndex].spec); + QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); + QMap<SortingKey, QString>::const_iterator end = objectUuids.constEnd(); + QMap<SortingKey, QString>::const_iterator i = objectUuids.upperBound(key); + int index = iterator_position(begin, end, i); + if (index <= lastQueriedIndex) + lastQueriedIndex++; + + q->beginInsertRows(parent, index, index); + objectUuids.insert(key, uuid); + objectCache.insert(index, uuid, item, objectUuids); + partitionObjectUuids[partitionIndex].insert(key, uuid); + objectSortValues.insert(uuid, key); + q->endInsertRows(); + emit q->rowCountChanged(objectSortValues.count()); +} + + +// deleteitem notification handler +void QJsonDbQueryModelPrivate::deleteItem(const QJsonObject &item, int partitionIndex) +{ + Q_Q(QJsonDbQueryModel); + QString uuid = item.value(QLatin1String("_uuid")).toString(); + QMap<QString, SortingKey>::const_iterator keyIndex = objectSortValues.constFind(uuid); + if (keyIndex != objectSortValues.constEnd()) { + SortingKey key = keyIndex.value(); + QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); + QMap<SortingKey, QString>::const_iterator end = objectUuids.constEnd(); + QMap<SortingKey, QString>::const_iterator i = objectUuids.constFind(key); + if (i != end) { + int index = iterator_position(begin, end, i); + q->beginRemoveRows(parent, index, index); + objectCache.remove(index, uuid); + partitionObjectUuids[partitionIndex].remove(key); + objectUuids.remove(key); + objectSortValues.remove(uuid); + if (index == lastQueriedIndex) + lastQueriedIndex = -1; + else if (index < lastQueriedIndex) + lastQueriedIndex--; + q->endRemoveRows(); + emit q->rowCountChanged(objectUuids.count()); + } + } +} + +// updateitem notification handler +void QJsonDbQueryModelPrivate::updateItem(const QJsonObject &item, int partitionIndex) +{ + Q_Q(QJsonDbQueryModel); + QString uuid = item.value(QLatin1String("_uuid")).toString(); + QMap<QString, SortingKey>::const_iterator keyIndex = objectSortValues.constFind(uuid); + if (keyIndex != objectSortValues.constEnd()) { + SortingKey key = keyIndex.value(); + QVariantList vl; + vl.append(uuid); + vl.append(item.value(QLatin1String("_indexValue")).toVariant()); + SortingKey newKey(partitionIndex, vl, QList<bool>() << ascendingOrder, partitionIndexDetails[partitionIndex].spec); + QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); + QMap<SortingKey, QString>::const_iterator end = objectUuids.constEnd(); + QMap<SortingKey, QString>::const_iterator oldPos = objectUuids.constFind(key); + int oldIndex = iterator_position(begin, end, oldPos); + if (oldIndex == lastQueriedIndex) // Cached object has changed + lastQueriedIndex = -1; + // keys are same, modify the object + if (key == newKey) { + objectCache.update(uuid, item); + QModelIndex modelIndex = q->createIndex(oldIndex, 0); + emit q->dataChanged(modelIndex, modelIndex); + return; + } + // keys are different + QMap<SortingKey, QString>::const_iterator newPos = objectUuids.upperBound(newKey); + int newIndex = iterator_position(begin, end, newPos); + if ((newIndex != oldIndex) && (newIndex != oldIndex+1)) { + if (oldIndex < lastQueriedIndex && newIndex > lastQueriedIndex) + lastQueriedIndex--; + else if (oldIndex > lastQueriedIndex && newIndex <= lastQueriedIndex) + lastQueriedIndex++; + q->beginMoveRows(parent, oldIndex, oldIndex, parent, newIndex); + objectUuids.remove(key); + partitionObjectUuids[partitionIndex].remove(key); + objectCache.remove(oldIndex, uuid); + + objectUuids.insert(newKey, uuid); + partitionObjectUuids[partitionIndex].insert(newKey, uuid); + + objectSortValues.remove(uuid); + objectSortValues.insert(uuid, newKey); + + // recompute the new position + newPos = objectUuids.constFind(newKey); + begin = objectUuids.constBegin(); + end = objectUuids.constEnd(); + newIndex = iterator_position(begin, end, newPos); + objectCache.insert(newIndex, uuid, item, objectUuids); + q->endMoveRows(); + // send data changed and return + QModelIndex modelIndex = q->createIndex(newIndex, 0); + emit q->dataChanged(modelIndex, modelIndex); + } else { + // same position, update the object + objectCache.update(uuid, item); + objectUuids.remove(key); + objectUuids.insert(newKey, uuid); + partitionObjectUuids[partitionIndex].remove(key); + partitionObjectUuids[partitionIndex].insert(newKey, uuid); + objectSortValues.remove(uuid); + objectSortValues.insert(uuid, newKey); + newPos = objectUuids.constFind(newKey); + begin = objectUuids.constBegin(); + end = objectUuids.constEnd(); + newIndex = iterator_position(begin, end, newPos); + QModelIndex modelIndex = q->createIndex(newIndex, 0); + emit q->dataChanged(modelIndex, modelIndex); + } + } else { + addItem(item, partitionIndex); + } +} + +int findIndexOf(const JsonDbModelIndexType::const_iterator &begin, const SortingKey &key, int low, int high) +{ + if (high < low) + return -1; + int mid = (low + high) / 2; + JsonDbModelIndexType::const_iterator midItr = begin + mid; + if (midItr.key() == key) // == + return mid; + else if (midItr.key() < key) // < + return findIndexOf(begin, key, mid+1, high); + else // > + return findIndexOf(begin, key, low, mid-1); +} + +inline void setQueryBindings(QJsonDbReadRequest *request, const QVariantMap &bindings) +{ + QVariantMap::ConstIterator i = bindings.constBegin(); + while (i != bindings.constEnd()) { + request->bindValue(i.key(), QJsonValue::fromVariant(i.value())); + ++i; + } +} + +void QJsonDbQueryModelPrivate::createObjectRequests(int startIndex, int maxItems) +{ + Q_Q(QJsonDbQueryModel); + +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug()<<Q_FUNC_INFO<<startIndex<<maxItems<<objectUuids.count(); +#endif + Q_ASSERT(startIndex>=0); + + if (startIndex >= objectUuids.size()) + return; + if ((startIndex + maxItems) > objectUuids.size()) + maxItems = objectUuids.size() - startIndex; + + if (state == QJsonDbQueryModel::Querying && + currentCacheRequest.index == startIndex && + currentCacheRequest.count == maxItems) { + // we are fetching the same set, skip this +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug()<<"Skip this request"; +#endif + return; + } + currentCacheRequest.index = startIndex; + currentCacheRequest.count = maxItems; + +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug()<<"startIndex"<<startIndex<<" maxItems "<<maxItems; +#endif + JsonDbModelIndexNSize *indexNSizes = new JsonDbModelIndexNSize[partitionObjects.count()] ; + JsonDbModelIndexType::const_iterator itr = objectUuids.constBegin()+startIndex; + for (int i = startIndex; i < startIndex+maxItems; i++, itr++) { + const SortingKey &key = itr.key(); + int index = key.partitionIndex(); + if (indexNSizes[index].index == -1) { + indexNSizes[index].index = findIndexOf(partitionObjectUuids[index].constBegin(), + key, 0, partitionObjectUuids[index].count()-1); + Q_ASSERT(indexNSizes[index].index != -1); + } + indexNSizes[index].count++; + } + for (int i = 0; i < partitionObjects.count(); i++) { + RequestInfo &r = partitionObjectDetails[i]; + if (indexNSizes[i].count) { + if (state != QJsonDbQueryModel::Querying) { + state = QJsonDbQueryModel::Querying; + emit q->stateChanged(state); + } + + r.lastOffset = indexNSizes[i].index; + r.lastSize = -1; + r.requestCount = indexNSizes[i].count; + QJsonDbReadRequest *request = valueRequests[i]->newRequest(i); + request->setQuery(query+sortOrder); + request->setProperty("queryOffset", indexNSizes[i].index); + request->setQueryLimit(qMin(r.requestCount, chunkSize)); + request->setPartition(partitionObjects[i]); + setQueryBindings(request, queryBindings); + if (mConnection) + mConnection->send(request); +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug()<<"Query"<<query+sortOrder<<partitionObjects[i]; + qDebug()<<"Request "<<request->property("requestId") << + "Total Count "<<r.requestCount << + "Offset"<<r.lastOffset<< + "Count "<<qMin(r.requestCount,chunkSize); +#endif + } else { + r.lastSize = 0; + r.requestCount = 0; + } + } + delete [] indexNSizes; +} + +void QJsonDbQueryModelPrivate::verifyIndexSpec(const QList<QJsonObject> &items, int partitionIndex) +{ + Q_Q(QJsonDbQueryModel); + SortIndexSpec &indexSpec = partitionIndexDetails[partitionIndex].spec; + bool validIndex = false; + if (items.count()) { + for (int i = 0; i < items.length() && !validIndex; i++) { + QJsonObject spec = items[i]; + indexSpec.propertyName = QLatin1String("_indexValue"); + QString propertyType = spec.value(QLatin1String("propertyType")).toString(); + indexSpec.name = spec.value(QLatin1String("name")).toString(); + if (indexSpec.name.isEmpty()) + indexSpec.name = spec.value(QLatin1String("propertyName")).toString(); + indexSpec.caseSensitive = true; + if (!indexName.isEmpty()) { + if (indexSpec.name == indexName) { + if (!propertyType.compare(QLatin1String("string"), Qt::CaseInsensitive)) { + indexSpec.type = SortIndexSpec::String; + if (spec.value(QLatin1String("caseSensitive")).isBool()) + indexSpec.caseSensitive = spec.value(QLatin1String("caseSensitive")).toBool(); + validIndex = true; + } else if (!propertyType.compare(QLatin1String("number"), Qt::CaseInsensitive)) { + indexSpec.type = SortIndexSpec::Number; + validIndex = true; + } else if (!propertyType.compare(QLatin1String("UUID"), Qt::CaseInsensitive)) { + indexSpec.type = SortIndexSpec::UUID; + indexSpec.caseSensitive = false; + validIndex = true; + } + } + } + } + } + if (!validIndex) { + qWarning() << "Error JsonDbCachingListModel requires a supported Index for "<<indexName << partitionObjects[partitionIndex]; + reset(); + state = QJsonDbQueryModel::Error; + emit q->stateChanged(state); + } else { + partitionIndexDetails[partitionIndex].valid = true; + //Check if all index specs are supported. + bool checkedAll = true; + for (int i = 0; i < partitionIndexDetails.count(); i++) { + if (availablePartitions[i].state != PartitionStateOnline) + continue; + if (!partitionIndexDetails[i].valid) { + checkedAll = false; + break; + } + } + if (checkedAll) { + //Start fetching the keys. + setQueryForSortKeys(); + for (int i = 0; i < partitionKeyRequestDetails.count(); i++) { + fetchPartitionKeys(i); + } + } + } +} + +void QJsonDbQueryModelPrivate::fillKeys(const QList<QJsonObject> &items, int partitionIndex) +{ + RequestInfo &r = partitionKeyRequestDetails[partitionIndex]; + r.lastSize = items.size(); + for (int i = 0; i < r.lastSize; i++) { + const QJsonObject &item = items.at(i); + QString uuidStr = item.value(QLatin1String("_uuid")).toString(); + QByteArray uuid = QUuid(uuidStr).toRfc4122(); + SortingKey key(partitionIndex, uuid, item.value(QLatin1String("_indexValue")).toVariant(), ascendingOrder, partitionIndexDetails[partitionIndex].spec); + objectUuids.insert(key, uuidStr); + partitionObjectUuids[partitionIndex].insert(key, uuidStr); + objectSortValues.insert(uuidStr, key); + + } + // Check if requests from different partitions returned + // all the results + bool allRequestsFinished = true; + for (int i = 0; i < partitionKeyRequestDetails.count(); i++) { + if (availablePartitions[i].state != PartitionStateOnline) + continue; + if (partitionKeyRequestDetails[i].lastSize >= chunkSize || partitionKeyRequestDetails[i].lastSize == -1) { + allRequestsFinished = false; + break; + } + } + if (allRequestsFinished) { + // retrieve the first chunk of data +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug()<<"All Keys Received "<<objectUuids.count(); +#endif + if (!objectUuids.count()) { + for (int i = 0; i<partitionObjectDetails.count(); i++) { + fillData(QList<QJsonObject>(), i); + } + return; + } + createObjectRequests(0, qMin(objectCache.maxItems(), objectUuids.count())); + } else if (r.lastSize >= chunkSize){ + // more items, fetch next chunk of keys + fetchNextKeyChunk(partitionIndex); + } +} + +void QJsonDbQueryModelPrivate::emitDataChanged(int from, int to) +{ + Q_Q(QJsonDbQueryModel); + QModelIndex modelIndexFrom = q->createIndex(from, 0); + QModelIndex modelIndexTo = q->createIndex(to, 0); + emit q->dataChanged(modelIndexFrom, modelIndexTo); +} + +void QJsonDbQueryModelPrivate::fillData(const QList<QJsonObject> &items, int partitionIndex) +{ + Q_Q(QJsonDbQueryModel); + RequestInfo &r = partitionObjectDetails[partitionIndex]; + r.lastSize = items.size(); + r.requestCount -= r.lastSize; + r.lastOffset += r.lastSize; + + for (int i = 0; i < r.lastSize; i++) { + const QJsonObject &item = items.at(i); + const QString &uuid = item.value(QLatin1String("_uuid")).toString(); + tmpObjects.insert(uuid, item); + } + + // Check if requests from different partitions returned + // all the results + bool allRequestsFinished = true; + for (int i = 0; i < partitionObjectDetails.count(); i++) { + if (availablePartitions[i].state != PartitionStateOnline) + continue; + if (partitionObjectDetails[i].lastSize >= chunkSize || partitionObjectDetails[i].lastSize == -1) { + allRequestsFinished = false; + break; + } + } + if (allRequestsFinished) { +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug()<<"Finished Req For:"<<currentCacheRequest.index<<currentCacheRequest.count; + qDebug()<<"Finished Req received count: "<<tmpObjects.count()<<" Total Items:"<<objectUuids.count(); +#endif + objectCache.addObjects(currentCacheRequest.index, objectUuids, tmpObjects); + tmpObjects.clear(); + JsonDbModelIndexNSize req = currentCacheRequest; + currentCacheRequest.clear(); + // send the update for missed items + QList<int> pendingCacheMiss; + int changedFrom = -1, changedTo = -2; + for (int i = 0; i < cacheMiss.size(); i++) { + if (cacheMiss[i] >= req.index && + cacheMiss[i] < req.index + req.count) { + if (changedFrom >= 0 && cacheMiss[i] != changedTo+1) { + emitDataChanged(changedFrom, changedTo); + changedFrom = -1; + } + if (changedFrom < 0) changedFrom = cacheMiss[i]; + changedTo = cacheMiss[i]; + } else { + pendingCacheMiss.append(cacheMiss[i]); + } + } + if (changedFrom >= 0) + emitDataChanged(changedFrom, changedTo); + cacheMiss.clear(); + cacheMiss = pendingCacheMiss; + + for (int i = 0; i<req.count; i++) { + emit q->objectAvailable(req.index + i, + getJsonObject(req.index + i), + getItemPartition(req.index + i)); + } + + if (resetModel) { + q->beginResetModel(); + q->endResetModel(); + emit q->rowCountChanged(objectUuids.count()); + resetModel = false; + } + // retrieved all elements + state = QJsonDbQueryModel::Ready; + emit q->stateChanged(state); + if (!pendingNotifications.isEmpty()) { + foreach (NotificationItem pending, pendingNotifications) + sendNotification(pending.partitionIndex, pending.item, pending.action); + pendingNotifications.clear(); + } + if (requestQueue.count()) { + QPair<int, int> req = requestQueue.takeFirst(); + createObjectRequests(req.first, req.second); + } + } else if (r.lastSize >= chunkSize){ + // more items, fetch next chunk + fetchNextChunk(partitionIndex); + } +} + + +//Clears all the state information. +void QJsonDbQueryModelPrivate::reset() +{ + Q_Q(QJsonDbQueryModel); + lastQueriedIndex = -1; + q->beginResetModel(); + clearNotifications(); + for (int i = 0; i < partitionObjectDetails.count(); i++) { + partitionObjectDetails[i].clear(); + } + for (int i = 0; i < partitionKeyRequestDetails.count(); i++) { + partitionKeyRequestDetails[i].clear(); + } + for (int i = 0; i < partitionIndexDetails.count(); i++) { + partitionIndexDetails[i].clear(); + } + for (int i = 0; i < partitionObjectUuids.count(); i++) { + partitionObjectUuids[i].clear(); + } + + objectCache.clear(); + objectUuids.clear(); + objectSortValues.clear(); + currentCacheRequest.clear(); + cacheMiss.clear(); + requestQueue.clear(); + q->endResetModel(); + emit q->rowCountChanged(0); + state = QJsonDbQueryModel::None; + emit q->stateChanged(state); +} + +bool QJsonDbQueryModelPrivate::checkForDefaultIndexTypes(int index) +{ + Q_Q(QJsonDbQueryModel); + bool defaultType = false; + if (!indexName.compare(QLatin1String("_uuid")) || !indexName.compare(QLatin1String("_type"))) { + defaultType = true; + QMetaObject::invokeMethod(q, "_q_verifyDefaultIndexType", Qt::QueuedConnection, + QGenericReturnArgument(), + Q_ARG(int, index)); + } + return defaultType; +} + +void QJsonDbQueryModelPrivate::fetchIndexSpec(int index) +{ + Q_Q(QJsonDbQueryModel); + if (index >= partitionObjects.count()) + return; + if (checkForDefaultIndexTypes(index)) + return; + if (state != QJsonDbQueryModel::Querying) { + state = QJsonDbQueryModel::Querying; + emit q->stateChanged(state); + } + if (availablePartitions[index].state == PartitionStateOnline) { + QString partitionName = partitionObjects[index]; + QJsonDbReadRequest *request = indexRequests[index]->newRequest(index); + request->setQuery(queryForIndexSpec); + request->setPartition(partitionName); + if (mConnection) + mConnection->send(request); + } +} + +void QJsonDbQueryModelPrivate::fetchPartitionKeys(int index) +{ + Q_Q(QJsonDbQueryModel); + if (index >= partitionObjects.count()) + return; + + if (state != QJsonDbQueryModel::Querying) { + state = QJsonDbQueryModel::Querying; + emit q->stateChanged(state); + } + if (availablePartitions[index].state == PartitionStateOnline) { + RequestInfo &r = partitionKeyRequestDetails[index]; + QString partitionName = partitionObjects[index]; + r.lastSize = -1; + r.lastOffset = 0; + QJsonDbReadRequest *request = keyRequests[index]->newRequest(index); + request->setQuery(queryForSortKeys); + request->setQueryLimit(chunkSize); + request->setPartition(partitionName); + setQueryBindings(request, queryBindings); + if (mConnection) + mConnection->send(request); + } +} + +void QJsonDbQueryModelPrivate::initializeModel(bool reset) +{ + resetModel = reset; + if (resetModel) { + objectCache.clear(); + objectUuids.clear(); + objectSortValues.clear(); + for (int i = 0; i < partitionObjectUuids.count(); i++) { + partitionObjectUuids[i].clear(); + } + for (int i = 0; i < partitionIndexDetails.count(); i++) { + partitionIndexDetails[i].clear(); + } + } + for (int i = 0; i < partitionObjects.count(); i++) { + fetchIndexSpec(i); + } +} + +void QJsonDbQueryModelPrivate::fetchModel(bool reset) +{ + parseSortOrder(); + initializeModel(reset); +} + +void QJsonDbQueryModelPrivate::fetchNextKeyChunk(int partitionIndex) +{ + RequestInfo &r = partitionKeyRequestDetails[partitionIndex]; + r.lastOffset += chunkSize; + QJsonDbReadRequest *request = keyRequests[partitionIndex]->newRequest(partitionIndex); + request->setQuery(queryForSortKeys); + request->setProperty("queryOffset", r.lastOffset); + request->setQueryLimit(chunkSize); + request->setPartition(partitionObjects[partitionIndex]); + setQueryBindings(request, queryBindings); + if (mConnection) + mConnection->send(request); +} + +void QJsonDbQueryModelPrivate::fetchNextChunk(int partitionIndex) +{ + RequestInfo &r = partitionObjectDetails[partitionIndex]; + QJsonDbReadRequest *request = valueRequests[partitionIndex]->newRequest(partitionIndex); + request->setQuery(query+sortOrder); + request->setProperty("queryOffset", r.lastOffset); + request->setQueryLimit(qMin(r.requestCount, chunkSize)); + request->setPartition(partitionObjects[partitionIndex]); + setQueryBindings(request, queryBindings); + if (mConnection) + mConnection->send(request); +} + +void QJsonDbQueryModelPrivate::prefetchNearbyPages(int index) +{ + int pos = objectCache.findPrefetchIndex(index, lowWaterMark); + if (pos != -1 && index <= objectUuids.count()) { + createObjectRequests(pos, objectCache.findChunkSize(pos)); + } +} +void QJsonDbQueryModelPrivate::addIndexToQueue(int index) +{ + int maxItems = 0; + int start = objectCache.findIndexNSize(index, maxItems); + QPair<int, int> req; + foreach (req, requestQueue) { + if (start == req.first && maxItems == req.second) { +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug()<<"Allready in Queue "<<start<<maxItems; +#endif + return; + } + } + requestQueue.append(QPair<int, int>(start,maxItems)); +} + +void QJsonDbQueryModelPrivate::requestPageContaining(int index) +{ +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug()<<Q_FUNC_INFO<<index; +#endif + if (state == QJsonDbQueryModel::Querying) { + if (index >= currentCacheRequest.index && + index < currentCacheRequest.index+currentCacheRequest.count) { + // Check if we are querying for this range already + if (!cacheMiss.contains(index)) + cacheMiss.append(index); + return; + } else { +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug()<<"Add new Request to Queue" << index <<" currentCacheRequest = " << + currentCacheRequest.index << ", " << currentCacheRequest.count; +#endif + addIndexToQueue(index); + return; + } + } + int maxItems = 0; + int start = objectCache.findIndexNSize(index, maxItems); + createObjectRequests(start, maxItems); + +} + +void QJsonDbQueryModelPrivate::clearNotification(int index) +{ + if (index >= partitionObjects.count()) + return; + + RequestInfo &r = partitionObjectDetails[index]; + if (r.watcher && mConnection) { + mConnection->removeWatcher(r.watcher); + } + r.clear(); +} + +void QJsonDbQueryModelPrivate::clearNotifications() +{ + for (int i = 0; i < partitionObjects.count(); i++) + clearNotification(i); +} + +void QJsonDbQueryModelPrivate::createOrUpdateNotification(int index) +{ + Q_Q(QJsonDbQueryModel); + if (index >= partitionObjects.count()) + return; + clearNotification(index); + if (availablePartitions[index].state != PartitionStateOnline) + return; + QJsonDbWatcher *watcher = new QJsonDbWatcher(); + watcher->setQuery(query+sortOrder); + watcher->setWatchedActions(QJsonDbWatcher::Created | QJsonDbWatcher::Updated |QJsonDbWatcher::Removed); + watcher->setPartition(partitionObjects[index]); + QVariantMap::ConstIterator i = queryBindings.constBegin(); + while (i != queryBindings.constEnd()) { + watcher->bindValue(i.key(), QJsonValue::fromVariant(i.value())); + ++i; + } + QObject::connect(watcher, SIGNAL(notificationsAvailable(int)), + q, SLOT(_q_notificationsAvailable())); + QObject::connect(watcher, SIGNAL(error(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)), + q, SLOT(_q_notificationError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString))); + if (mConnection) { + mConnection->addWatcher(watcher); + partitionObjectDetails[index].watcher = watcher; + } +} + +void QJsonDbQueryModelPrivate::createOrUpdateNotifications() +{ + for (int i = 0; i < partitionObjects.count(); i++) { + createOrUpdateNotification(i); + } +} + +void QJsonDbQueryModelPrivate::parseSortOrder() +{ + Q_Q(QJsonDbQueryModel); + QRegExp orderMatch(QStringLiteral("\\[([/\\\\[\\]])[ ]*([^\\[\\]]+)[ ]*\\]")); + if (orderMatch.indexIn(sortOrder, 0) >= 0) { + ascendingOrder = false; + if (!orderMatch.cap(1).compare(QLatin1String("/"))) + ascendingOrder = true; + indexName = orderMatch.cap(2); + } + if (!indexName.isEmpty()) { + queryForIndexSpec = QString(QLatin1String("[?_type=\"Index\"][?name=\"%1\" | propertyName=\"%1\"]")).arg(indexName); + } else { + // Set default sort order (by _uuid) + q->setSortOrder(QLatin1String("[/_uuid]")); + } +} + +void QJsonDbQueryModelPrivate::setQueryForSortKeys() +{ + // Query to retrieve the sortKeys + // TODO remove the "[= {}]" from query + queryForSortKeys = query + QLatin1String("[= { _uuid: _uuid, _indexValue: _indexValue }]"); + queryForSortKeys += sortOrder; +} + +int QJsonDbQueryModelPrivate::indexOfWatcher(QJsonDbWatcher *watcher) +{ + for (int i = 0; i < partitionObjectDetails.count(); i++) { + if (watcher == partitionObjectDetails[i].watcher) + return i; + } + return -1; +} + +int QJsonDbQueryModelPrivate::indexOfPartitionObjectWatcher(QJsonDbWatcher *watcher) +{ + for (int i = 0; i < availablePartitions.count(); i++) { + if (watcher == availablePartitions[i].watcher) + return i; + } + return -1; +} + +QJsonObject QJsonDbQueryModelPrivate::getJsonObject(int index) +{ + if (index == lastQueriedIndex) + return lastQueriedObject; + if (index < 0 || index >= objectUuids.size()) { +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug() << "getItem" << index << "size " << objectUuids.size(); +#endif + return QJsonObject(); + } + int page = objectCache.findPage(index); + if (page == -1) { + if (!cacheMiss.contains(index)) + cacheMiss.append(index); + requestPageContaining(index); + return QJsonObject(); + } + + QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); + const QString &uuid = (begin+index).value(); + if (!objectCache.hasValueAtPage(page, uuid)) { + // The value is missing, refresh page + int startIndex = 0; int count = 0; + objectCache.dropPage(page, startIndex, count); +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug() << "getItem Refresh Page: "<<page<<"Start: "<<startIndex<< "Count:"<<count<< "State :"<<state; +#endif + if (state == QJsonDbQueryModel::Ready) { + // Start the request + createObjectRequests(startIndex, count); + } else { + requestQueue.append(QPair<int, int>(startIndex, count)); + } + if (!cacheMiss.contains(index)) + cacheMiss.append(index); + return QJsonObject(); + } + if (state == QJsonDbQueryModel::Ready) // Pre-fetch only, if in Ready state + prefetchNearbyPages(index); + QJsonObject ret = objectCache.valueAtPage(page, uuid); + lastQueriedIndex = index; + lastQueriedObject = ret; + return ret; +} + +QVariant QJsonDbQueryModelPrivate::getItem(int index) +{ + return QVariant(getJsonObject(index).toVariantMap()); +} + +QVariant QJsonDbQueryModelPrivate::getItem(int index, int role) +{ + QJsonObject obj = getJsonObject(index); + return lookupJsonProperty(obj, properties[role]).toVariant(); +} + +QString QJsonDbQueryModelPrivate::getItemPartition(int index) +{ + if (index < 0 || index >= objectUuids.size()) + return QString(); + QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); + int partitionIndex = (begin+index).key().partitionIndex(); + if (partitionIndex <= partitionObjects.count()) + return partitionObjects[partitionIndex]; + return QString(); +} + +int QJsonDbQueryModelPrivate::indexOf(const QString &uuid) const +{ + if (!objectSortValues.contains(uuid)) + return -1; + const SortingKey &key = objectSortValues.value(uuid); + QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); + QMap<SortingKey, QString>::const_iterator end = objectUuids.constEnd(); + QMap<SortingKey, QString>::const_iterator i = objectUuids.find(key); + return iterator_position(begin, end, i); +} + +void QJsonDbQueryModelPrivate::sendNotification(int partitionIndex, const QJsonObject &object, QJsonDbWatcher::Action action) +{ + if (action == QJsonDbWatcher::Created) { + addItem(object, partitionIndex); + } else if (action == QJsonDbWatcher::Removed) + deleteItem(object, partitionIndex); + else if (action == QJsonDbWatcher::Updated) { + updateItem(object, partitionIndex); + } +} + +void QJsonDbQueryModelPrivate::onPartitionStateChanged() +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif +#ifdef JSONDB_LISTMODEL_DEBUG + qDebug() << Q_FUNC_INFO; +#endif + if (componentComplete && !query.isEmpty() && partitionsReady()) { + createOrUpdateNotifications(); + fetchModel(); + } + +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +bool QJsonDbQueryModelPrivate::partitionsReady() +{ + for (int i = 0; i < partitionObjects.count(); i++) { + if (availablePartitions[i].state == PartitionStateNone || availablePartitions[i].state == PartitionStateError) + return false; + } + return true; +} + +void QJsonDbQueryModelPrivate::_q_keyResponse(int index, const QList<QJsonObject> &v, const QString &sortKey) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_UNUSED(sortKey) + fillKeys(v, index); +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::_q_valueResponse(int index, const QList<QJsonObject> &v) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + fillData(v, index); +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::_q_indexResponse(int index, const QList<QJsonObject> &v) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + verifyIndexSpec(v, index); +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::_q_readError(QtJsonDb::QJsonDbRequest::ErrorCode code, const QString & message) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_Q(QJsonDbQueryModel); + qWarning() << QString(QStringLiteral("JsonDb error: %1 %2")).arg(code).arg(message); + if (code != QtJsonDb::QJsonDbRequest::PartitionUnavailable) { + int oldErrorCode = errorCode; + errorCode = code; + errorString = message; + if (oldErrorCode != errorCode) + emit q->errorChanged(q->error()); + } +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::_q_notificationsAvailable() +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_Q(QJsonDbQueryModel); + QJsonDbWatcher *watcher = qobject_cast<QJsonDbWatcher *>(q->sender()); + int partitionIndex = indexOfWatcher(watcher); + if (!watcher || partitionIndex == -1) + return; + QList<QJsonDbNotification> list = watcher->takeNotifications(); + for (int i = 0; i < list.count(); i++) { + const QJsonDbNotification & notification = list[i]; + QJsonObject object = notification.object(); + QJsonDbWatcher::Action action = notification.action(); + if (state == QJsonDbQueryModel::Querying) { + NotificationItem pending; + pending.partitionIndex = partitionIndex; + pending.item = object; + pending.action = action; + pendingNotifications.append(pending); + } else { + foreach (NotificationItem pending, pendingNotifications) + sendNotification(pending.partitionIndex, pending.item, pending.action); + pendingNotifications.clear(); + sendNotification(partitionIndex, object, action); + } + } +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::_q_partitionWatcherNotificationsAvailable() +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_Q(QJsonDbQueryModel); + QJsonDbWatcher *watcher = qobject_cast<QJsonDbWatcher *>(q->sender()); + int partitionIndex = indexOfPartitionObjectWatcher(watcher); + if (!watcher || partitionIndex == -1) + return; + QList<QJsonDbNotification> list = watcher->takeNotifications(); + for (int i = 0; i < list.count(); i++) { + const QJsonDbNotification & notification = list[i]; + QJsonObject object = notification.object(); + QJsonDbWatcher::Action action = notification.action(); + QJsonDbQueryModelPrivate::JsonDbPartitionState previousState = availablePartitions[partitionIndex].state; + if (action == QJsonDbWatcher::Removed) { + availablePartitions[partitionIndex].state = PartitionStateOffline; + } else { + availablePartitions[partitionIndex].state = object.value(QStringLiteral("available")) + .toBool() ? PartitionStateOnline : PartitionStateOffline; + } + if (previousState != availablePartitions[partitionIndex].state) + this->onPartitionStateChanged(); + } +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::_q_notificationError(QtJsonDb::QJsonDbWatcher::ErrorCode code, const QString &message) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_Q(QJsonDbQueryModel); + if (code != QtJsonDb::QJsonDbRequest::PartitionUnavailable) { + int oldErrorCode = errorCode; + errorCode = code; + errorString = message; + if (oldErrorCode != errorCode) + emit q->errorChanged(q->error()); + } +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::_q_partitionObjectQueryFinished() +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_Q(QJsonDbQueryModel); + QJsonDbReadRequest *request = qobject_cast<QJsonDbReadRequest *>(q->sender()); + if (request) { + QList<QJsonObject> objects = request->takeResults(); + int count = objects.count(); + if (count) { + QString name = objects[0].value(QStringLiteral("name")).toString(); + // Skip this if name has been changed already + int partitionIndex = partitionObjects.indexOf(name); + if (partitionIndex == -1) + return; + JsonDbPartitionState state = objects[0].value(QStringLiteral("available")) + .toBool() ? PartitionStateOnline : PartitionStateOffline; + availablePartitions[partitionIndex].state = state; + onPartitionStateChanged(); + } + } +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::_q_partitionObjectQueryError() +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_Q(QJsonDbQueryModel); + QJsonDbReadRequest *request = qobject_cast<QJsonDbReadRequest *>(q->sender()); + //TODO: NEED A MAPPING BETWEEN REQUEST AND PARTITION NAME / INDEX + if (request) { + qWarning() << "Partition query error"; + } +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::_q_partitionWatcherNotificationError(QtJsonDb::QJsonDbWatcher::ErrorCode code, const QString &message) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_Q(QJsonDbQueryModel); + QJsonDbWatcher *watcher = qobject_cast<QJsonDbWatcher *>(q->sender()); + int partitionIndex = indexOfPartitionObjectWatcher(watcher); + qWarning() << QStringLiteral("QJsonDbQueryModel PartitionObjectNotification error: %1 %2").arg(code).arg(message); + availablePartitions[partitionIndex].state = PartitionStateError; +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + + + +void QJsonDbQueryModelPrivate::_q_verifyDefaultIndexType(int index) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + SortIndexSpec &indexSpec = partitionIndexDetails[index].spec; + partitionIndexDetails[index].valid = true; + if (!indexName.compare(QLatin1String("_uuid"))) { + indexSpec.name = QLatin1String("_uuid"); + indexSpec.type = SortIndexSpec::UUID; + indexSpec.caseSensitive = false; + } else if (!indexName.compare(QLatin1String("_type"))) { + indexSpec.name = QLatin1String("_type"); + indexSpec.type = SortIndexSpec::String; + indexSpec.caseSensitive = true; + } + //Check if all index specs are supported. + bool checkedAll = true; + for (int i = 0; i < partitionIndexDetails.count(); i++) { + if (!partitionIndexDetails[i].valid) { + checkedAll = false; + break; + } + } + if (checkedAll) { + //Start fetching the keys. + setQueryForSortKeys(); + for (int i = 0; i < partitionKeyRequestDetails.count(); i++) { + fetchPartitionKeys(i); + } + } +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +void QJsonDbQueryModelPrivate::appendPartition(const QString& partitionName) +{ + Q_Q(QJsonDbQueryModel); + partitionObjects.append(partitionName); + + partitionObjectDetails.append(RequestInfo()); + startWatchingPartitionObject(partitionName); + ModelRequest *valueRequest = new ModelRequest(); + QObject::connect(valueRequest, SIGNAL(finished(int,QList<QJsonObject>,QString)), + q, SLOT(_q_valueResponse(int,QList<QJsonObject>))); + QObject::connect(valueRequest, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)), + q, SLOT(_q_readError(QtJsonDb::QJsonDbRequest::ErrorCode,QString))); + valueRequests.append(valueRequest); + + partitionKeyRequestDetails.append(RequestInfo()); + ModelRequest *keyRequest = new ModelRequest(); + QObject::connect(keyRequest, SIGNAL(finished(int,QList<QJsonObject>,QString)), + q, SLOT(_q_keyResponse(int,QList<QJsonObject>,QString))); + QObject::connect(keyRequest, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)), + q, SLOT(_q_readError(QtJsonDb::QJsonDbRequest::ErrorCode,QString))); + keyRequests.append(keyRequest); + + partitionObjectUuids.append(JsonDbModelIndexType()); + + partitionIndexDetails.append(IndexInfo()); + ModelRequest *indexRequest = new ModelRequest(); + QObject::connect(indexRequest, SIGNAL(finished(int,QList<QJsonObject>,QString)), + q, SLOT(_q_indexResponse(int,QList<QJsonObject>))); + QObject::connect(indexRequest, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)), + q, SLOT(_q_readError(QtJsonDb::QJsonDbRequest::ErrorCode,QString))); + indexRequests.append(indexRequest); + + if (componentComplete && !query.isEmpty()) { + parseSortOrder(); + createOrUpdateNotification(partitionObjects.count()-1); + if (state == QJsonDbQueryModel::None) + resetModel = true; + fetchIndexSpec(partitionObjects.count()-1); + } +} + +void QJsonDbQueryModelPrivate::startWatchingPartitionObject(const QString &partitionName) +{ + Q_Q(QJsonDbQueryModel); + availablePartitions.append(JsonDbAvailablePartitionsInfo()); + + QString query; + if (partitionName.isEmpty()) + query= QLatin1String("[?_type=\"Partition\"][?default=true]"); + else + query = QStringLiteral("[?_type=\"Partition\"][?name=\"%1\"]").arg(partitionName); + + // Create a watcher to watch changes in partition state + QJsonDbWatcher *partitionWatcher = new QJsonDbWatcher(); + partitionWatcher->setQuery(query); + partitionWatcher->setWatchedActions(QJsonDbWatcher::Created | QJsonDbWatcher::Removed | QJsonDbWatcher::Updated); + partitionWatcher->setPartition(QStringLiteral("Ephemeral")); + QObject::connect(partitionWatcher, SIGNAL(notificationsAvailable(int)), + q, SLOT(_q_partitionWatcherNotificationsAvailable())); + QObject::connect(partitionWatcher, SIGNAL(error(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)), + q, SLOT(_q_partitionWatcherNotificationError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString))); + mConnection->addWatcher(partitionWatcher); + + // Create a query to ephemeral partition to find out the state (&name) of the partition + QJsonDbReadRequest *request = new QJsonDbReadRequest; + request->setQuery(query); + request->setPartition(QStringLiteral("Ephemeral")); + QObject::connect(request, SIGNAL(finished()), q, SLOT(_q_partitionObjectQueryFinished())); + QObject::connect(request, SIGNAL(finished()), request, SLOT(deleteLater())); + QObject::connect(request, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)), + q, SLOT(_q_partitionObjectQueryError())); + QObject::connect(request, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)), + request, SLOT(deleteLater())); + mConnection->send(request); +} + +void QJsonDbQueryModelPrivate::clearPartitions() +{ + partitionObjects.clear(); + partitionObjectDetails.clear(); + partitionKeyRequestDetails.clear(); + partitionObjectUuids.clear(); + partitionIndexDetails.clear(); + while (!keyRequests.isEmpty()) { + delete keyRequests[0]; + keyRequests.removeFirst(); + } + while (!indexRequests.isEmpty()) { + delete indexRequests[0]; + indexRequests.removeFirst(); + } + while (!valueRequests.isEmpty()) { + delete valueRequests[0]; + valueRequests.removeFirst(); + } + reset(); +} + +/*! + \class QJsonDbQueryModel + \inmodule QtJsonDb + + The QJsonDbQueryModel provides a read-only QAbstractListModel usable with views such as + ListView or GridView displaying data items matching a query. The sorting is done using + an index set on the JsonDb server. If it doesn't find a matching index for the sortkey, + the model goes into Error state. Maximum number of items in the model cache can be set + by cacheSize property. + + When an item is not present in the internal cache, the model can return an 'undefined' + object from data() method. It will be queued for retrieval and the model will notify its + presence using the dataChanged() signal. + + The model is initialized by retrieving the result in chunks. After receiving the first + chunk, the model is reset with items from it. The state will be "Querying" during + fetching data and will be changed to "Ready". + + \code + QtJsonDb::QJsonDbQueryModel *model = new QtJsonDb::QJsonDbQueryModel(connection); + model->setQueryRoleNames(roleMap); + model->appendPartition(QStringLiteral("User")); + model->setQuery(QStringLiteral("[?_type=\"MyType\"][/orderKey]"); + model->populate(); + \endcode + + \sa setQueryRoleNames(), appendPartition(), setQuery(), populate() +*/ +/*! + \enum QJsonDbQueryModel::state + + This enum describes current model state. + + \value None Query returned zero items. + \value Querying Model has issued the query but not received a response. + \value Ready Model has received items. + \value Error Model received an error response from the partitions. +*/ + +QJsonDbQueryModel::QJsonDbQueryModel(QJsonDbConnection *dbConnection, QObject *parent) + : QAbstractListModel(parent) + , d_ptr(new QJsonDbQueryModelPrivate(this)) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_D(QJsonDbQueryModel); + d->init(dbConnection); +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +QJsonDbQueryModel::~QJsonDbQueryModel() +{ +} + +/*! + Loads objects matching the specified query from the specified + partitions into the model cache. +*/ +void QJsonDbQueryModel::populate() +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_D(QJsonDbQueryModel); + d->componentComplete = true; + if (!d->query.isEmpty() && d->partitionObjects.count() && d->partitionsReady()) { + d->createOrUpdateNotifications(); + d->fetchModel(); + } +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +/*! + \property QJsonDbQueryModel::rowCount + Indicates how many items match the query. Value is zero unless state is Ready. +*/ +int QJsonDbQueryModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + Q_D(const QJsonDbQueryModel); + return d->objectUuids.count(); +} + +/*! + Returns the \a role of the object at \a modelIndex. + + \sa setQueryRoleNames() + */ +QVariant QJsonDbQueryModel::data(const QModelIndex &modelIndex, int role) const +{ + QVariant ret; + int index = modelIndex.row(); + return data (index, role); +} + +QVariant QJsonDbQueryModel::data(int index, int role) const +{ + Q_D(const QJsonDbQueryModel); +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + QVariant ret; + if (index < 0 || index >= d->objectUuids.size()) + ret = QVariant(); + // Special synchronous handling for _uuid and _indexValue + else if (d->properties[role].at(0) == QLatin1String("_uuid")) { + JsonDbModelIndexType::const_iterator itr = d->objectUuids.constBegin() + index; + ret = itr.value(); + } + else if (d->properties[role].at(0) == QLatin1String("_indexValue")) { + JsonDbModelIndexType::const_iterator itr = d->objectUuids.constBegin() + index; + ret = itr.key().value(); + } + else + ret = const_cast<QJsonDbQueryModel*>(this)->d_func()->getItem(index, role); +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif + return ret; +} + +QHash<int, QByteArray> QJsonDbQueryModel::roleNames() const +{ + Q_D(const QJsonDbQueryModel); + return d->roleNames; +} + +/*! + \property QJsonDbQueryModel::queryRoleNames + + Controls which properties to expose from the objects matching the query. + + Setting \a queryRoleNames to a list of strings causes the model to expose + corresponding object values as roles to the delegate for each item viewed. + + \code + TBD + \endcode + + Setting \a queryRoleNames to a dictionary remaps properties in the object + to the specified roles in the model. + + In the following example, role \a a would yield the value of + property \a aLongName in the objects. Role \a liftedProperty would + yield the value of \a o.nested.property for each matching object \a + o in the database. + + \code + TBD + \endcode + + */ + +QVariant QJsonDbQueryModel::queryRoleNames() const +{ + Q_D(const QJsonDbQueryModel); + return d->roleMap; +} + +void QJsonDbQueryModel::setQueryRoleNames(const QVariant &vroles) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_D(QJsonDbQueryModel); + d->properties.clear(); + d->roleNames.clear(); + if (vroles.type() == QVariant::Map) { + QVariantMap roles = vroles.toMap(); + d->roleMap = roles; + int i = 0; + for (QVariantMap::const_iterator it = roles.begin(); it != roles.end(); ++it) { + d->roleNames.insert(i, it.key().toLatin1()); + d->properties.insert(i, removeArrayOperator(it.value().toString()).split('.')); + i++; + } + } else { + QVariantList roleList = vroles.toList(); + d->roleMap.clear(); + for (int i = 0; i < roleList.size(); i++) { + QString role = roleList[i].toString(); + d->roleMap[role] = role; + d->roleNames.insert(i, role.toLatin1()); + d->properties.insert(i, removeArrayOperator(role).split('.')); + } + } + QAbstractItemModel::setRoleNames(d->roleNames); +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +/*! + \property QJsonDbQueryModel::query + + The query string in JsonQuery format used by the model to fetch + items from the database. Setting an empty query clears all the elements + + In the following example, the JsonDbCachingListModel would contain all + the objects with \a _type "CONTACT" from partition called "com.nokia.shared" + + \sa QtJsonDb::QJsonDbQueryModel::bindings + */ +QString QJsonDbQueryModel::query() const +{ + Q_D(const QJsonDbQueryModel); + return d->query; +} + +void QJsonDbQueryModel::setQuery(const QString &newQuery) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_D(QJsonDbQueryModel); + + const QString oldQuery = d->query; + if (oldQuery == newQuery) + return; + + d->query = newQuery; + if (rowCount() && d->query.isEmpty()) { + d->reset(); + } + + if (!d->componentComplete || d->query.isEmpty() || !d->partitionObjects.count() || !d->partitionsReady()) + return; + d->createOrUpdateNotifications(); + d->fetchModel(); +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +/*! + \property QJsonDbQueryModel::bindings() + + Holds the bindings for the placeholders used in the query string. Note that + the placeholder marker '%' should not be included as part of the keys. + + \sa QtJsonDb::QJsonDbQueryModel::query() + */ +QVariantMap QJsonDbQueryModel::bindings() const +{ + Q_D(const QJsonDbQueryModel); + return d->queryBindings; +} + +void QJsonDbQueryModel::setBindings(const QVariantMap &newBindings) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_D(QJsonDbQueryModel); + d->queryBindings = newBindings; + + if (!d->componentComplete || d->query.isEmpty() || !d->partitionObjects.count() || !d->partitionsReady()) + return; + d->createOrUpdateNotifications(); + d->fetchModel(); +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + + +/*! + \property QJsonDbQueryModel::cacheSize + Holds the maximum number of objects hold in memory by the model. +*/ +int QJsonDbQueryModel::cacheSize() const +{ + Q_D(const QJsonDbQueryModel); + return d->cacheSize; +} + +void QJsonDbQueryModel::setCacheSize(int newCacheSize) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_D(QJsonDbQueryModel); + if (newCacheSize == d->cacheSize) + return; + + d->cacheSize = newCacheSize; + d->setCacheParams(d->cacheSize); + if (!d->componentComplete || d->query.isEmpty() || !d->partitionObjects.count() || !d->partitionsReady()) + return; + + d->fetchModel(); +#ifdef JSONDB_LISTMODEL_DEBUG + d->objectCache.dumpCacheDetails(); +#endif +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +/*! + \property QJsonDbQueryModel::sortOrder + + The order used by the model to sort the items. Make sure that there + is a matching Index in the database for this sortOrder. This has to be + specified in the JsonQuery format. + + In the following example, the QJsonDbQueryModel would contain all + the objects of type \a "Contact" sorted by their \a firstName field + + \qml + JsonDb.JsonDbCachingListModel { + id: listModel + query: "[?_type=\"Contact\"]" + partitions:[ JsonDb.Partition { + name:"com.nokia.shared" + }] + sortOrder: "[/firstName]" + } + \endqml + + \sa QtJsonDb::QJsonDbQueryModel::bindings +*/ +QString QJsonDbQueryModel::sortOrder() const +{ + Q_D(const QJsonDbQueryModel); + return d->sortOrder; +} + +void QJsonDbQueryModel::setSortOrder(const QString &newSortOrder) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_D(QJsonDbQueryModel); + + const QString oldSortOrder = d->sortOrder; + d->sortOrder = newSortOrder; + if (oldSortOrder != newSortOrder) { + d->parseSortOrder(); + if (!d->componentComplete || d->query.isEmpty() || !d->partitionObjects.count() || !d->partitionsReady()) + return; + d->createOrUpdateNotifications(); + d->fetchModel(); + } +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + +/*! + \property QJsonDbQueryModel::state + The current state of the model. + \list + \li QJsonDbQueryModel::None - The model is not initialized + \li QJsonDbQueryModel::Querying - It is querying the results from server + \li QJsonDbQueryModel::Ready - Results are ready + \li QJsonDbQueryModel::Error - Cannot find a matching index on the server + \endlist + +*/ +QJsonDbQueryModel::State QJsonDbQueryModel::state() const +{ + Q_D(const QJsonDbQueryModel); + return d->state; +} + +/*! + Returns the index of the object with \a uuid from the model. + + Becaues the model caches all uuids the index can be returned + immediately. + + \sa QJsonDbQueryModel::uuid() +*/ +int QJsonDbQueryModel::indexOf(const QString &uuid) const +{ + Q_D(const QJsonDbQueryModel); + return d->indexOf(uuid); +} + +/*! + Fetches the object at position \a index in the model. + + Becaues the model caches objects, it may not have a copy of the + object in memory. In that case, it queries the appropriate + partition to fetch the object. + + The model emits signal objectAvailable() with \a index, the + object, and the name of the partition containing the object. + + \sa QJsonDbQueryModel::objectAvailable() + \sa QJsonDbQueryModel::uuid() +*/ +void QJsonDbQueryModel::fetchObject(int index) +{ +#ifdef JSONDB_LISTMODEL_BENCHMARK + QElapsedTimer elt; + elt.start(); +#endif + Q_D(QJsonDbQueryModel); + if (index < 0 || index >= d->objectUuids.size()) + return; + int page = d->objectCache.findPage(index); + if (page == -1) { + d->requestPageContaining(index); + return; + } + QJsonObject result = d->getJsonObject(index); + QString partitionName = d->getItemPartition(index); + emit objectAvailable(index, result, partitionName); +#ifdef JSONDB_LISTMODEL_BENCHMARK + qint64 elap = elt.elapsed(); + if (elap > 3) + qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; +#endif +} + + +/*! + Returns the name of partition at position \a index in the list of partition names used by the model. +*/ +QString QJsonDbQueryModel::partitionName(int index) const +{ + QJsonDbQueryModel *pThis = const_cast<QJsonDbQueryModel *>(this); + return pThis->d_func()->getItemPartition(index); +} + +/*! + Returns the list of partition names used by the model. +*/ +QStringList QJsonDbQueryModel::partitionNames() const +{ + QJsonDbQueryModel *pThis = const_cast<QJsonDbQueryModel *>(this); + return QStringList(pThis->d_func()->partitionObjects); +} + +/*! + Sets the list of partition names used by the model to \a partitionNames. +*/ +void QJsonDbQueryModel::setPartitionNames(const QStringList &partitionNames) +{ + QJsonDbQueryModel *pThis = const_cast<QJsonDbQueryModel *>(this); + pThis->d_func()->clearPartitions(); + foreach (const QString &partitionName, partitionNames) { + pThis->d_func()->appendPartition(partitionName); + } +} + +/*! + Add a partition named \a partitionName to the list of partition names used by the model. +*/ +void QJsonDbQueryModel::appendPartitionName(const QString &partitionName) +{ + Q_D(QJsonDbQueryModel); + d->appendPartition(partitionName); +} + +/*! + \property QJsonDbQueryModel::error +*/ +QVariantMap QJsonDbQueryModel::error() const +{ + Q_D(const QJsonDbQueryModel); + QVariantMap errorMap; + errorMap.insert(QLatin1String("code"), d->errorCode); + errorMap.insert(QLatin1String("message"), d->errorString); + return errorMap; +} + +#include "moc_qjsondbquerymodel_p.cpp" +QT_END_NAMESPACE_JSONDB diff --git a/src/client/qjsondbquerymodel_p.h b/src/client/qjsondbquerymodel_p.h new file mode 100644 index 0000000..b864783 --- /dev/null +++ b/src/client/qjsondbquerymodel_p.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtAddOn.JsonDb module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QJSONDBQUERYMODEL_P_H +#define QJSONDBQUERYMODEL_P_H + +#include <QAbstractListModel> +#include <QHash> +#include <QMultiMap> +#include <QSet> +#include <QSharedDataPointer> +#include <QStringList> +#include <QScopedPointer> +#include <QJsonObject> +#include <QtJsonDb/qjsondbglobal.h> + +QT_BEGIN_HEADER + +QT_BEGIN_NAMESPACE_JSONDB + +class QJsonDbConnection; + +class QJsonDbQueryModelPrivate; + +class Q_JSONDB_EXPORT QJsonDbQueryModel: public QAbstractListModel +{ + Q_OBJECT + Q_ENUMS(State) +public: + enum State { None, Querying, Ready, Error }; + + Q_PROPERTY(QVariantMap bindings READ bindings WRITE setBindings) + Q_PROPERTY(int cacheSize READ cacheSize WRITE setCacheSize) + Q_PROPERTY(QVariantMap error READ error NOTIFY errorChanged) + Q_PROPERTY(QString query READ query WRITE setQuery) + Q_PROPERTY(QVariant roleNames READ queryRoleNames WRITE setQueryRoleNames) + Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged) + Q_PROPERTY(QString sortOrder READ sortOrder WRITE setSortOrder) + Q_PROPERTY(State state READ state NOTIFY stateChanged) + + QJsonDbQueryModel(QJsonDbConnection *dbConnection, QObject *parent = 0); + virtual ~QJsonDbQueryModel(); + + //From QAbstractItemModel + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QVariant data(int index, int role = Qt::DisplayRole) const; + virtual QHash<int,QByteArray> roleNames() const; + + QVariantMap error() const; + + QVariantMap bindings() const; + void setBindings(const QVariantMap &newBindings); + int cacheSize() const; + void setCacheSize(int newCacheSize); + + int indexOf(const QString &uuid) const; + // fetchObject is async (see objectAvailable -signal) as the object at + // specifiec index might not be in model cache + void fetchObject(int index); + + QString query() const; + void setQuery(const QString &newQuery); + + QVariant queryRoleNames() const; + void setQueryRoleNames(const QVariant &roles); + + QString sortOrder() const; + void setSortOrder(const QString &newSortOrder); + + State state() const; + + void populate(); + + //Partition handling + QString partitionName(int index) const; + QStringList partitionNames() const; + void setPartitionNames(const QStringList &partitions); + void appendPartitionName(const QString &partitionName); + +Q_SIGNALS: + void stateChanged(State state) const; + void rowCountChanged(int newCount) const; + void errorChanged(QVariantMap); + void objectAvailable(int index, QJsonObject availableObject, QString objectPartition); + +private: + Q_DISABLE_COPY(QJsonDbQueryModel) + Q_DECLARE_PRIVATE(QJsonDbQueryModel) + QScopedPointer<QJsonDbQueryModelPrivate> d_ptr; + + Q_PRIVATE_SLOT(d_func(), void _q_verifyDefaultIndexType(int)) + Q_PRIVATE_SLOT(d_func(), void _q_notificationsAvailable()) + Q_PRIVATE_SLOT(d_func(), void _q_partitionWatcherNotificationsAvailable()) + Q_PRIVATE_SLOT(d_func(), void _q_notificationError(QtJsonDb::QJsonDbWatcher::ErrorCode, QString)) + Q_PRIVATE_SLOT(d_func(), void _q_partitionWatcherNotificationError(QtJsonDb::QJsonDbWatcher::ErrorCode, QString)) + Q_PRIVATE_SLOT(d_func(), void _q_partitionObjectQueryFinished()) + Q_PRIVATE_SLOT(d_func(), void _q_partitionObjectQueryError()) + Q_PRIVATE_SLOT(d_func(), void _q_keyResponse(int, QList<QJsonObject>, QString)) + Q_PRIVATE_SLOT(d_func(), void _q_valueResponse(int, QList<QJsonObject>)) + Q_PRIVATE_SLOT(d_func(), void _q_indexResponse(int, QList<QJsonObject>)) + Q_PRIVATE_SLOT(d_func(), void _q_readError(QtJsonDb::QJsonDbRequest::ErrorCode, QString)) +}; + +QT_END_HEADER + +QT_END_NAMESPACE_JSONDB + +#endif // QJSONDBQUERYMODEL_P_H diff --git a/src/imports/jsondb/jsondbcachinglistmodel_p.h b/src/client/qjsondbquerymodel_p_p.h index cf9d707..f5959a3 100644 --- a/src/imports/jsondb/jsondbcachinglistmodel_p.h +++ b/src/client/qjsondbquerymodel_p_p.h @@ -40,8 +40,8 @@ ****************************************************************************/ -#ifndef JSONDBCACHINGLISTMODEL_P_H -#define JSONDBCACHINGLISTMODEL_P_H +#ifndef QJSONDBQUERYMODEL_P_P_H +#define QJSONDBQUERYMODEL_P_P_H #include <QHash> #include <QMultiMap> @@ -52,10 +52,11 @@ #include <QUuid> #include <QJsonObject> -#include "jsondatabase.h" -#include "jsondbcachinglistmodel.h" -#include "jsondbmodelutils.h" -#include "jsondbmodelcache.h" +#include "qjsondbquerymodel_p.h" +#include "qjsondbmodelutils_p.h" +#include "qjsondbmodelcache_p.h" + +QT_BEGIN_HEADER QT_BEGIN_NAMESPACE_JSONDB @@ -72,14 +73,23 @@ struct JsonDbModelIndexNSize } }; -class JsonDbCachingListModelPrivate +struct JsonDbAvailablePartitionsInfo; + +class QJsonDbQueryModelPrivate { - Q_DECLARE_PUBLIC(JsonDbCachingListModel) + Q_DECLARE_PUBLIC(QJsonDbQueryModel) + public: - JsonDbCachingListModel *q_ptr; + enum JsonDbPartitionState { PartitionStateNone, + PartitionStateOnline, + PartitionStateOffline, + PartitionStateError }; + QJsonDbQueryModel *q_ptr; + QJsonDbConnection *mConnection; QList<RequestInfo> partitionObjectDetails; - QList<QPointer<JsonDbPartition> >partitionObjects; + QList<QString> partitionObjects; + QList<JsonDbAvailablePartitionsInfo> availablePartitions; bool componentComplete; bool resetModel; @@ -112,14 +122,14 @@ public: QHash<int, QStringList> properties; QList<NotificationItem> pendingNotifications; + QList<NotificationItem> pendingPartitionObjectNotifications; QList<int> cacheMiss; - QMap<int, QJSValue> getCallbacks; QList< QPair<int,int> > requestQueue; QList< QPointer<ModelRequest> >keyRequests; QList< QPointer<ModelRequest> >indexRequests; QList< QPointer<ModelRequest> >valueRequests; - JsonDbCachingListModel::State state; + QJsonDbQueryModel::State state; QModelIndex parent; int errorCode; QString errorString; @@ -132,10 +142,9 @@ public: QJsonObject lastQueriedObject; public: - JsonDbCachingListModelPrivate(JsonDbCachingListModel *q); - ~JsonDbCachingListModelPrivate(); - void init(); - bool partitionsReady(); + QJsonDbQueryModelPrivate(QJsonDbQueryModel *q); + ~QJsonDbQueryModelPrivate(); + void init(QJsonDbConnection *dbConnection); void setCacheParams(int maxItems); void createObjectRequests(int startIndex, int maxItems); @@ -170,39 +179,51 @@ public: void verifyIndexSpec(const QList<QJsonObject> &items, int partitionIndex); int indexOfWatcher(QJsonDbWatcher *watcher); + int indexOfPartitionObjectWatcher(QJsonDbWatcher *watcher); - void initPartition(JsonDbPartition *v); - void appendPartition(JsonDbPartition *v); + void appendPartition(const QString& partitionName); void clearPartitions(); + void startWatchingPartitionObject(const QString& partitionName); + void onPartitionStateChanged(); + bool partitionsReady(); QJsonObject getJsonObject(int index); QVariant getItem(int index); QVariant getItem(int index, int role); - void queueGetCallback(int index, const QJSValue &callback); - void callGetCallback(int index, QJSValue callback); - JsonDbPartition* getItemPartition(int index); + QString getItemPartition(int index); int indexOf(const QString &uuid) const; - void set(int index, const QJSValue& valuemap, - const QJSValue &successCallback, - const QJSValue &errorCallback); void sendNotification(int partitionIndex, const QJsonObject &object, QJsonDbWatcher::Action action); // private slots void _q_verifyDefaultIndexType(int index); void _q_notificationsAvailable(); + void _q_partitionWatcherNotificationsAvailable(); + void _q_partitionWatcherNotificationError(QtJsonDb::QJsonDbWatcher::ErrorCode code, const QString &message); void _q_notificationError(QtJsonDb::QJsonDbWatcher::ErrorCode code, const QString &message); + void _q_partitionObjectQueryFinished(); + void _q_partitionObjectQueryError(); void _q_readError(QtJsonDb::QJsonDbRequest::ErrorCode code, const QString & message); void _q_keyResponse(int , const QList<QJsonObject>&, const QString&); void _q_valueResponse(int , const QList<QJsonObject>&); void _q_indexResponse(int , const QList<QJsonObject>&); - void _q_partitionStateChanged(JsonDbPartition::State state); - - static void partitions_append(QQmlListProperty<JsonDbPartition> *p, JsonDbPartition *v); - static int partitions_count(QQmlListProperty<JsonDbPartition> *p); - static JsonDbPartition* partitions_at(QQmlListProperty<JsonDbPartition> *p, int idx); - static void partitions_clear(QQmlListProperty<JsonDbPartition> *p); +}; +struct JsonDbAvailablePartitionsInfo +{ + QPointer<QJsonDbWatcher> watcher; + QJsonDbQueryModelPrivate::JsonDbPartitionState state; + JsonDbAvailablePartitionsInfo() { clear();} + void clear() + { + state = QJsonDbQueryModelPrivate::PartitionStateNone; + if (watcher) { + delete watcher; + watcher = 0; + } + } }; QT_END_NAMESPACE_JSONDB -#endif // JSONDBCACHINGLISTMODEL_P_H +QT_END_HEADER + +#endif // QJSONDBQUERYMODEL_P_P_H diff --git a/src/imports/jsondb/jsondb.pro b/src/imports/jsondb/jsondb.pro index 580c5d9..586aa1b 100644 --- a/src/imports/jsondb/jsondb.pro +++ b/src/imports/jsondb/jsondb.pro @@ -34,13 +34,10 @@ HEADERS += \ plugin.h \ jsondatabase.h \ jsondbqueryobject.h \ - jsondbmodelutils.h \ - jsondbmodelcache.h \ jsondblistmodel.h \ jsondblistmodel_p.h \ jsondbsortinglistmodel_p.h \ jsondbsortinglistmodel.h \ - jsondbcachinglistmodel_p.h \ jsondbcachinglistmodel.h SOURCES += \ @@ -49,8 +46,6 @@ SOURCES += \ plugin.cpp \ jsondatabase.cpp \ jsondbqueryobject.cpp \ - jsondbmodelutils.cpp \ - jsondbmodelcache.cpp \ jsondblistmodel.cpp \ jsondbsortinglistmodel.cpp \ jsondbcachinglistmodel.cpp diff --git a/src/imports/jsondb/jsondbcachinglistmodel.cpp b/src/imports/jsondb/jsondbcachinglistmodel.cpp index ae87edd..e2a338c 100644 --- a/src/imports/jsondb/jsondbcachinglistmodel.cpp +++ b/src/imports/jsondb/jsondbcachinglistmodel.cpp @@ -42,9 +42,8 @@ //#define JSONDB_LISTMODEL_DEBUG //#define JSONDB_LISTMODEL_BENCHMARK -#include "jsondbcachinglistmodel.h" -#include "jsondbcachinglistmodel_p.h" #include "plugin.h" +#include "jsondbcachinglistmodel.h" #include <QJSEngine> #include <QJSValueIterator> @@ -60,1310 +59,189 @@ QT_BEGIN_NAMESPACE_JSONDB -JsonDbCachingListModelPrivate::JsonDbCachingListModelPrivate(JsonDbCachingListModel *q) - : q_ptr(q) - , componentComplete(false) - , resetModel(true) - , cacheSize(-1) - , state(JsonDbCachingListModel::None) - , errorCode(0) - , lastQueriedIndex(-1) -{ - setCacheParams(INT_MAX/10); -} - -void JsonDbCachingListModelPrivate::init() -{ -} - -void JsonDbCachingListModelPrivate::setCacheParams(int maxItems) -{ - objectCache.setPageSize(maxItems); - chunkSize = objectCache.chunkSize(); - lowWaterMark = objectCache.chunkSize()/4; -} - -JsonDbCachingListModelPrivate::~JsonDbCachingListModelPrivate() -{ - clearNotifications(); - while (!keyRequests.isEmpty()) { - delete keyRequests[0]; - keyRequests.removeFirst(); - } - while (!indexRequests.isEmpty()) { - delete indexRequests[0]; - indexRequests.removeFirst(); - } - while (!valueRequests.isEmpty()) { - delete valueRequests[0]; - valueRequests.removeFirst(); - } - -} - -bool JsonDbCachingListModelPrivate::partitionsReady() -{ - for (int i = 0; i < partitionObjects.count(); i++) { - if (partitionObjects[i]->state() == JsonDbPartition::None || partitionObjects[i]->state() == JsonDbPartition::Error) - return false; - } - return true; -} - -// insert item notification handler -// + add items, for chunked read -void JsonDbCachingListModelPrivate::addItem(const QJsonObject &item, int partitionIndex) -{ - Q_Q(JsonDbCachingListModel); - const QString &uuid = item.value(QLatin1String("_uuid")).toString(); - // ignore duplicates. - if (objectSortValues.contains(uuid)) - return; - - QVariantList vl; - vl.append(uuid); - vl.append(item.value(QLatin1String("_indexValue")).toVariant()); - SortingKey key(partitionIndex, vl, QList<bool>() << ascendingOrder, partitionIndexDetails[partitionIndex].spec); - QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); - QMap<SortingKey, QString>::const_iterator end = objectUuids.constEnd(); - QMap<SortingKey, QString>::const_iterator i = objectUuids.upperBound(key); - int index = iterator_position(begin, end, i); - if (index <= lastQueriedIndex) - lastQueriedIndex++; - - q->beginInsertRows(parent, index, index); - objectUuids.insert(key, uuid); - objectCache.insert(index, uuid, item, objectUuids); - partitionObjectUuids[partitionIndex].insert(key, uuid); - objectSortValues.insert(uuid, key); - q->endInsertRows(); - emit q->rowCountChanged(objectSortValues.count()); -} - - -// deleteitem notification handler -void JsonDbCachingListModelPrivate::deleteItem(const QJsonObject &item, int partitionIndex) -{ - Q_Q(JsonDbCachingListModel); - QString uuid = item.value(QLatin1String("_uuid")).toString(); - QMap<QString, SortingKey>::const_iterator keyIndex = objectSortValues.constFind(uuid); - if (keyIndex != objectSortValues.constEnd()) { - SortingKey key = keyIndex.value(); - QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); - QMap<SortingKey, QString>::const_iterator end = objectUuids.constEnd(); - QMap<SortingKey, QString>::const_iterator i = objectUuids.constFind(key); - if (i != end) { - int index = iterator_position(begin, end, i); - q->beginRemoveRows(parent, index, index); - objectCache.remove(index, uuid); - partitionObjectUuids[partitionIndex].remove(key); - objectUuids.remove(key); - objectSortValues.remove(uuid); - if (index == lastQueriedIndex) - lastQueriedIndex = -1; - else if (index < lastQueriedIndex) - lastQueriedIndex--; - q->endRemoveRows(); - emit q->rowCountChanged(objectUuids.count()); - } - } -} - -// updateitem notification handler -void JsonDbCachingListModelPrivate::updateItem(const QJsonObject &item, int partitionIndex) -{ - Q_Q(JsonDbCachingListModel); - QString uuid = item.value(QLatin1String("_uuid")).toString(); - QMap<QString, SortingKey>::const_iterator keyIndex = objectSortValues.constFind(uuid); - if (keyIndex != objectSortValues.constEnd()) { - SortingKey key = keyIndex.value(); - QVariantList vl; - vl.append(uuid); - vl.append(item.value(QLatin1String("_indexValue")).toVariant()); - SortingKey newKey(partitionIndex, vl, QList<bool>() << ascendingOrder, partitionIndexDetails[partitionIndex].spec); - QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); - QMap<SortingKey, QString>::const_iterator end = objectUuids.constEnd(); - QMap<SortingKey, QString>::const_iterator oldPos = objectUuids.constFind(key); - int oldIndex = iterator_position(begin, end, oldPos); - if (oldIndex == lastQueriedIndex) // Cached object has changed - lastQueriedIndex = -1; - // keys are same, modify the object - if (key == newKey) { - objectCache.update(uuid, item); - QModelIndex modelIndex = q->createIndex(oldIndex, 0); - emit q->dataChanged(modelIndex, modelIndex); - return; - } - // keys are different - QMap<SortingKey, QString>::const_iterator newPos = objectUuids.upperBound(newKey); - int newIndex = iterator_position(begin, end, newPos); - if ((newIndex != oldIndex) && (newIndex != oldIndex+1)) { - if (oldIndex < lastQueriedIndex && newIndex > lastQueriedIndex) - lastQueriedIndex--; - else if (oldIndex > lastQueriedIndex && newIndex <= lastQueriedIndex) - lastQueriedIndex++; - q->beginMoveRows(parent, oldIndex, oldIndex, parent, newIndex); - objectUuids.remove(key); - partitionObjectUuids[partitionIndex].remove(key); - objectCache.remove(oldIndex, uuid); - - objectUuids.insert(newKey, uuid); - partitionObjectUuids[partitionIndex].insert(newKey, uuid); - - objectSortValues.remove(uuid); - objectSortValues.insert(uuid, newKey); - - // recompute the new position - newPos = objectUuids.constFind(newKey); - begin = objectUuids.constBegin(); - end = objectUuids.constEnd(); - newIndex = iterator_position(begin, end, newPos); - objectCache.insert(newIndex, uuid, item, objectUuids); - q->endMoveRows(); - // send data changed and return - QModelIndex modelIndex = q->createIndex(newIndex, 0); - emit q->dataChanged(modelIndex, modelIndex); - } else { - // same position, update the object - objectCache.update(uuid, item); - objectUuids.remove(key); - objectUuids.insert(newKey, uuid); - partitionObjectUuids[partitionIndex].remove(key); - partitionObjectUuids[partitionIndex].insert(newKey, uuid); - objectSortValues.remove(uuid); - objectSortValues.insert(uuid, newKey); - newPos = objectUuids.constFind(newKey); - begin = objectUuids.constBegin(); - end = objectUuids.constEnd(); - newIndex = iterator_position(begin, end, newPos); - QModelIndex modelIndex = q->createIndex(newIndex, 0); - emit q->dataChanged(modelIndex, modelIndex); - } - } else { - addItem(item, partitionIndex); - } -} - -int findIndexOf(const JsonDbModelIndexType::const_iterator &begin, const SortingKey &key, int low, int high) -{ - if (high < low) - return -1; - int mid = (low + high) / 2; - JsonDbModelIndexType::const_iterator midItr = begin + mid; - if (midItr.key() == key) // == - return mid; - else if (midItr.key() < key) // < - return findIndexOf(begin, key, mid+1, high); - else // > - return findIndexOf(begin, key, low, mid-1); -} - -inline void setQueryBindings(QJsonDbReadRequest *request, const QVariantMap &bindings) -{ - QVariantMap::ConstIterator i = bindings.constBegin(); - while (i != bindings.constEnd()) { - request->bindValue(i.key(), QJsonValue::fromVariant(i.value())); - ++i; - } -} - -void JsonDbCachingListModelPrivate::createObjectRequests(int startIndex, int maxItems) -{ - Q_Q(JsonDbCachingListModel); - -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug()<<Q_FUNC_INFO<<startIndex<<maxItems<<objectUuids.count(); -#endif - Q_ASSERT(startIndex>=0); - - if (startIndex >= objectUuids.size()) - return; - if ((startIndex + maxItems) > objectUuids.size()) - maxItems = objectUuids.size() - startIndex; - - if (state == JsonDbCachingListModel::Querying && - currentCacheRequest.index == startIndex && - currentCacheRequest.count == maxItems) { - // we are fetching the same set, skip this -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug()<<"Skip this request"; -#endif - return; - } - currentCacheRequest.index = startIndex; - currentCacheRequest.count = maxItems; - -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug()<<"startIndex"<<startIndex<<" maxItems "<<maxItems; -#endif - JsonDbModelIndexNSize *indexNSizes = new JsonDbModelIndexNSize[partitionObjects.count()] ; - JsonDbModelIndexType::const_iterator itr = objectUuids.constBegin()+startIndex; - for (int i = startIndex; i < startIndex+maxItems; i++, itr++) { - const SortingKey &key = itr.key(); - int index = key.partitionIndex(); - if (indexNSizes[index].index == -1) { - indexNSizes[index].index = findIndexOf(partitionObjectUuids[index].constBegin(), - key, 0, partitionObjectUuids[index].count()-1); - Q_ASSERT(indexNSizes[index].index != -1); - } - indexNSizes[index].count++; - } - for (int i = 0; i < partitionObjects.count(); i++) { - RequestInfo &r = partitionObjectDetails[i]; - if (indexNSizes[i].count) { - if (state != JsonDbCachingListModel::Querying) { - state = JsonDbCachingListModel::Querying; - emit q->stateChanged(state); - } - - r.lastOffset = indexNSizes[i].index; - r.lastSize = -1; - r.requestCount = indexNSizes[i].count; - QJsonDbReadRequest *request = valueRequests[i]->newRequest(i); - request->setQuery(query+sortOrder); - request->setProperty("queryOffset", indexNSizes[i].index); - request->setQueryLimit(qMin(r.requestCount, chunkSize)); - request->setPartition(partitionObjects[i]->name()); - setQueryBindings(request, queryBindings); - JsonDatabase::sharedConnection().send(request); -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug()<<"Query"<<query+sortOrder<<partitionObjects[i]->name(); - qDebug()<<"Request "<<request->property("requestId") << - "Total Count "<<r.requestCount << - "Offset"<<r.lastOffset<< - "Count "<<qMin(r.requestCount,chunkSize); -#endif - } else { - r.lastSize = 0; - r.requestCount = 0; - } - } - delete [] indexNSizes; -} - -void JsonDbCachingListModelPrivate::verifyIndexSpec(const QList<QJsonObject> &items, int partitionIndex) -{ - Q_Q(JsonDbCachingListModel); - SortIndexSpec &indexSpec = partitionIndexDetails[partitionIndex].spec; - bool validIndex = false; - if (items.count()) { - for (int i = 0; i < items.length() && !validIndex; i++) { - QJsonObject spec = items[i]; - indexSpec.propertyName = QLatin1String("_indexValue"); - QString propertyType = spec.value(QLatin1String("propertyType")).toString(); - indexSpec.name = spec.value(QLatin1String("name")).toString(); - if (indexSpec.name.isEmpty()) - indexSpec.name = spec.value(QLatin1String("propertyName")).toString(); - indexSpec.caseSensitive = true; - if (!indexName.isEmpty()) { - if (indexSpec.name == indexName) { - if (!propertyType.compare(QLatin1String("string"), Qt::CaseInsensitive)) { - indexSpec.type = SortIndexSpec::String; - if (spec.value(QLatin1String("caseSensitive")).isBool()) - indexSpec.caseSensitive = spec.value(QLatin1String("caseSensitive")).toBool(); - validIndex = true; - } else if (!propertyType.compare(QLatin1String("number"), Qt::CaseInsensitive)) { - indexSpec.type = SortIndexSpec::Number; - validIndex = true; - } else if (!propertyType.compare(QLatin1String("UUID"), Qt::CaseInsensitive)) { - indexSpec.type = SortIndexSpec::UUID; - indexSpec.caseSensitive = false; - validIndex = true; - } - } - } - } - } - if (!validIndex) { - qWarning() << "Error JsonDbCachingListModel requires a supported Index for "<<indexName << partitionObjects[partitionIndex]->name(); - reset(); - state = JsonDbCachingListModel::Error; - emit q->stateChanged(state); - } else { - partitionIndexDetails[partitionIndex].valid = true; - //Check if all index specs are supported. - bool checkedAll = true; - for (int i = 0; i < partitionIndexDetails.count(); i++) { - if (partitionObjects[i]->state() != JsonDbPartition::Online) - continue; - if (!partitionIndexDetails[i].valid) { - checkedAll = false; - break; - } - } - if (checkedAll) { - //Start fetching the keys. - setQueryForSortKeys(); - for (int i = 0; i < partitionKeyRequestDetails.count(); i++) { - fetchPartitionKeys(i); - } - } - } -} - -void JsonDbCachingListModelPrivate::fillKeys(const QList<QJsonObject> &items, int partitionIndex) -{ - RequestInfo &r = partitionKeyRequestDetails[partitionIndex]; - r.lastSize = items.size(); - for (int i = 0; i < r.lastSize; i++) { - const QJsonObject &item = items.at(i); - QString uuidStr = item.value(QLatin1String("_uuid")).toString(); - QByteArray uuid = QUuid(uuidStr).toRfc4122(); - SortingKey key(partitionIndex, uuid, item.value(QLatin1String("_indexValue")).toVariant(), ascendingOrder, partitionIndexDetails[partitionIndex].spec); - objectUuids.insert(key, uuidStr); - partitionObjectUuids[partitionIndex].insert(key, uuidStr); - objectSortValues.insert(uuidStr, key); - - } - // Check if requests from different partitions returned - // all the results - bool allRequestsFinished = true; - for (int i = 0; i < partitionKeyRequestDetails.count(); i++) { - if (partitionObjects[i]->state() != JsonDbPartition::Online) - continue; - if (partitionKeyRequestDetails[i].lastSize >= chunkSize || partitionKeyRequestDetails[i].lastSize == -1) { - allRequestsFinished = false; - break; - } - } - if (allRequestsFinished) { - // retrieve the first chunk of data -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug()<<"All Keys Received "<<objectUuids.count(); -#endif - if (!objectUuids.count()) { - for (int i = 0; i<partitionObjectDetails.count(); i++) { - fillData(QList<QJsonObject>(), i); - } - return; - } - createObjectRequests(0, qMin(objectCache.maxItems(), objectUuids.count())); - } else if (r.lastSize >= chunkSize){ - // more items, fetch next chunk of keys - fetchNextKeyChunk(partitionIndex); - } -} - -void JsonDbCachingListModelPrivate::emitDataChanged(int from, int to) -{ - Q_Q(JsonDbCachingListModel); - QModelIndex modelIndexFrom = q->createIndex(from, 0); - QModelIndex modelIndexTo = q->createIndex(to, 0); - emit q->dataChanged(modelIndexFrom, modelIndexTo); -} - -void JsonDbCachingListModelPrivate::fillData(const QList<QJsonObject> &items, int partitionIndex) -{ - Q_Q(JsonDbCachingListModel); - RequestInfo &r = partitionObjectDetails[partitionIndex]; - r.lastSize = items.size(); - r.requestCount -= r.lastSize; - r.lastOffset += r.lastSize; - for (int i = 0; i < r.lastSize; i++) { - const QJsonObject &item = items.at(i); - const QString &uuid = item.value(QLatin1String("_uuid")).toString(); - tmpObjects.insert(uuid, item); - } +/*! + \qmlclass JsonDbCachingListModel JsonDbCachingListModel + \inqmlmodule QtJsonDb + \since 1.0 - // Check if requests from different partitions returned - // all the results - bool allRequestsFinished = true; - for (int i = 0; i < partitionObjectDetails.count(); i++) { - if (partitionObjects[i]->state() != JsonDbPartition::Online) - continue; - if (partitionObjectDetails[i].lastSize >= chunkSize || partitionObjectDetails[i].lastSize == -1) { - allRequestsFinished = false; - break; - } - } - if (allRequestsFinished) { -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug()<<"Finished Req For:"<<currentCacheRequest.index<<currentCacheRequest.count; - qDebug()<<"Finished Req recieved count: "<<tmpObjects.count()<<" Total Items:"<<objectUuids.count(); -#endif - objectCache.addObjects(currentCacheRequest.index, objectUuids, tmpObjects); - tmpObjects.clear(); - JsonDbModelIndexNSize req = currentCacheRequest; - currentCacheRequest.clear(); - // send the update for missed items - QList<int> pendingCacheMiss; - int changedFrom = -1, changedTo = -2; - for (int i = 0; i < cacheMiss.size(); i++) { - if (cacheMiss[i] >= req.index && - cacheMiss[i] < req.index + req.count) { - if (changedFrom >= 0 && cacheMiss[i] != changedTo+1) { - emitDataChanged(changedFrom, changedTo); - changedFrom = -1; - } - if (changedFrom < 0) changedFrom = cacheMiss[i]; - changedTo = cacheMiss[i]; - } else { - pendingCacheMiss.append(cacheMiss[i]); - } - } - if (changedFrom >= 0) - emitDataChanged(changedFrom, changedTo); - cacheMiss.clear(); - cacheMiss = pendingCacheMiss; - // call the get callback - QMap<int, QJSValue> pendingGetCallbacks; - QMapIterator<int, QJSValue> itr(getCallbacks); - while (itr.hasNext()) { - itr.next(); - int index = itr.key(); - if (index >= req.index && index < req.index + req.count) { - callGetCallback(index, itr.value()); - } else { - pendingGetCallbacks.insert(index, itr.value()); - } - } - getCallbacks.clear(); - getCallbacks = pendingGetCallbacks; - if (resetModel) { - q->beginResetModel(); - q->endResetModel(); - emit q->rowCountChanged(objectUuids.count()); - resetModel = false; - } - // retrieved all elements - state = JsonDbCachingListModel::Ready; - emit q->stateChanged(state); - if (!pendingNotifications.isEmpty()) { - foreach (NotificationItem pending, pendingNotifications) - sendNotification(pending.partitionIndex, pending.item, pending.action); - pendingNotifications.clear(); - } - if (requestQueue.count()) { - QPair<int, int> req = requestQueue.takeFirst(); - createObjectRequests(req.first, req.second); - } - } else if (r.lastSize >= chunkSize){ - // more items, fetch next chunk - fetchNextChunk(partitionIndex); - } -} + The JsonDbCachingListModel provides a read-only ListModel usable with views such as + ListView or GridView displaying data items matching a query. The sorting is done using + an index set on the JsonDb server. If it doesn't find a matching index for the sortkey, + the model goes into Error status. Maximum number of items in the model cache can be set + by cacheSize property. + When an item is not present in the internal cache, the model can return an 'undefined' + object from data() method. It will be queued for retrieval and the model will notify its + presence using the dataChanged() signal. -//Clears all the state information. -void JsonDbCachingListModelPrivate::reset() -{ - Q_Q(JsonDbCachingListModel); - lastQueriedIndex = -1; - q->beginResetModel(); - clearNotifications(); - for (int i = 0; i < partitionObjectDetails.count(); i++) { - partitionObjectDetails[i].clear(); - } - for (int i = 0; i < partitionKeyRequestDetails.count(); i++) { - partitionKeyRequestDetails[i].clear(); - } - for (int i = 0; i < partitionIndexDetails.count(); i++) { - partitionIndexDetails[i].clear(); - } - for (int i = 0; i < partitionObjectUuids.count(); i++) { - partitionObjectUuids[i].clear(); - } + The model is initialized by retrieving the result in chunks. After receiving the first + chunk, the model is reset with items from it. The status will be "Querying" during + fetching data and will be changed to "Ready". - objectCache.clear(); - objectUuids.clear(); - objectSortValues.clear(); - currentCacheRequest.clear(); - cacheMiss.clear(); - getCallbacks.clear(); - requestQueue.clear(); - q->endResetModel(); - emit q->rowCountChanged(0); - state = JsonDbCachingListModel::None; - emit q->stateChanged(state); -} + \note This is still a work in progress, so expect minor changes. -bool JsonDbCachingListModelPrivate::checkForDefaultIndexTypes(int index) -{ - Q_Q(JsonDbCachingListModel); - bool defaultType = false; - if (!indexName.compare(QLatin1String("_uuid")) || !indexName.compare(QLatin1String("_type"))) { - defaultType = true; - QMetaObject::invokeMethod(q, "_q_verifyDefaultIndexType", Qt::QueuedConnection, - QGenericReturnArgument(), - Q_ARG(int, index)); + \code + JsonDb.JsonDbCachingListModel { + id: listModel + query: "[?_type=\"CONTACT\"]" + cacheSize: 100 + partitions: [JsonDb.Partition { + name:"com.nokia.shared" + }] } - return defaultType; -} + \endcode +*/ -void JsonDbCachingListModelPrivate::fetchIndexSpec(int index) +JsonDbCachingListModel::JsonDbCachingListModel(QObject *parent): + QJsonDbQueryModel(QJsonDbConnection::defaultConnection(), parent) { - Q_Q(JsonDbCachingListModel); - if (index >= partitionObjects.count()) - return; - if (checkForDefaultIndexTypes(index)) - return; - if (state != JsonDbCachingListModel::Querying) { - state = JsonDbCachingListModel::Querying; - emit q->stateChanged(state); - } - QPointer<JsonDbPartition> p = partitionObjects[index]; - if (p && p->state() == JsonDbPartition::Online) { - QJsonDbReadRequest *request = indexRequests[index]->newRequest(index); - request->setQuery(queryForIndexSpec); - request->setPartition(p->name()); - JsonDatabase::sharedConnection().send(request); - } + QJsonDbConnection::defaultConnection()->connectToServer(); } -void JsonDbCachingListModelPrivate::fetchPartitionKeys(int index) +JsonDbCachingListModel::~JsonDbCachingListModel() { - Q_Q(JsonDbCachingListModel); - if (index >= partitionObjects.count()) - return; - - if (state != JsonDbCachingListModel::Querying) { - state = JsonDbCachingListModel::Querying; - emit q->stateChanged(state); - } - RequestInfo &r = partitionKeyRequestDetails[index]; - QPointer<JsonDbPartition> p = partitionObjects[index]; - if (p && p->state() == JsonDbPartition::Online) { - r.lastSize = -1; - r.lastOffset = 0; - QJsonDbReadRequest *request = keyRequests[index]->newRequest(index); - request->setQuery(queryForSortKeys); - request->setQueryLimit(chunkSize); - request->setPartition(p->name()); - setQueryBindings(request, queryBindings); - JsonDatabase::sharedConnection().send(request); - } } -void JsonDbCachingListModelPrivate::initializeModel(bool reset) -{ - resetModel = reset; - if (resetModel) { - objectCache.clear(); - objectUuids.clear(); - objectSortValues.clear(); - for (int i = 0; i < partitionObjectUuids.count(); i++) { - partitionObjectUuids[i].clear(); - } - for (int i = 0; i < partitionIndexDetails.count(); i++) { - partitionIndexDetails[i].clear(); - } - } - for (int i = 0; i < partitionObjects.count(); i++) { - fetchIndexSpec(i); - } -} +//---------------- METHODS------------------------------ -void JsonDbCachingListModelPrivate::fetchModel(bool reset) +void JsonDbCachingListModel::classBegin() { - parseSortOrder(); - initializeModel(reset); } -void JsonDbCachingListModelPrivate::fetchNextKeyChunk(int partitionIndex) +void JsonDbCachingListModel::componentComplete() { - RequestInfo &r = partitionKeyRequestDetails[partitionIndex]; - r.lastOffset += chunkSize; - QJsonDbReadRequest *request = keyRequests[partitionIndex]->newRequest(partitionIndex); - request->setQuery(queryForSortKeys); - request->setProperty("queryOffset", r.lastOffset); - request->setQueryLimit(chunkSize); - request->setPartition(partitionObjects[partitionIndex]->name()); - setQueryBindings(request, queryBindings); - JsonDatabase::sharedConnection().send(request); - + populate(); + connect (this, SIGNAL(objectAvailable(int,QJsonObject,QString)), this, SLOT(onObjectAvailable(int,QJsonObject,QString))); } -void JsonDbCachingListModelPrivate::fetchNextChunk(int partitionIndex) -{ - RequestInfo &r = partitionObjectDetails[partitionIndex]; - QJsonDbReadRequest *request = valueRequests[partitionIndex]->newRequest(partitionIndex); - request->setQuery(query+sortOrder); - request->setProperty("queryOffset", r.lastOffset); - request->setQueryLimit(qMin(r.requestCount, chunkSize)); - request->setPartition(partitionObjects[partitionIndex]->name()); - setQueryBindings(request, queryBindings); - JsonDatabase::sharedConnection().send(request); - -} +/*! + \qmlmethod QtJsonDb::JsonDbCachingListModel::get(int index, function callback) + Calls the callback with object at the specified \a index in the model. The result.object property + contains the object in its raw form as returned by the query, the rolenames + are not applied. The object.partition is the partition for the returned. + If the index is out of range it returns an object with empty partition & object properties. -void JsonDbCachingListModelPrivate::prefetchNearbyPages(int index) -{ - int pos = objectCache.findPrefetchIndex(index, lowWaterMark); - if (pos != -1 && index <= objectUuids.count()) { - createObjectRequests(pos, objectCache.findChunkSize(pos)); - } -} -void JsonDbCachingListModelPrivate::addIndexToQueue(int index) -{ - int maxItems = 0; - int start = objectCache.findIndexNSize(index, maxItems); - QPair<int, int> req; - foreach (req, requestQueue) { - if (start == req.first && maxItems == req.second) { -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug()<<"Allready in Queue "<<start<<maxItems; -#endif - return; + \code + function updateCallback(error, response) { + if (error) { + console.log("Update Error :"+JSON.stringify(error)); + return, } - } - requestQueue.append(QPair<int, int>(start,maxItems)); -} - -void JsonDbCachingListModelPrivate::requestPageContaining(int index) -{ -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug()<<Q_FUNC_INFO<<index; -#endif - if (state == JsonDbCachingListModel::Querying) { - if (index >= currentCacheRequest.index && - index < currentCacheRequest.index+currentCacheRequest.count) { - // Check if we are querying for this range already - if (!cacheMiss.contains(index)) - cacheMiss.append(index); - return; - } else { -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug()<<"Add new Request to Queue" << index <<" currentCacheRequest = " << - currentCacheRequest.index << ", " << currentCacheRequest.count; -#endif - addIndexToQueue(index); - return; + console.log("Response from Update"); + console.log("response.id = "+response.id +" count = "+response.items.length); + for (var i = 0; i < response.items.length; i++) { + console.log("_uuid = "+response.items[i]._uuid +" ._version = "+response.items[i]._version); } } - int maxItems = 0; - int start = objectCache.findIndexNSize(index, maxItems); - createObjectRequests(start, maxItems); - -} - -void JsonDbCachingListModelPrivate::clearNotification(int index) -{ - if (index >= partitionObjects.count()) - return; - - RequestInfo &r = partitionObjectDetails[index]; - if (r.watcher) { - JsonDatabase::sharedConnection().removeWatcher(r.watcher); - } - r.clear(); -} - -void JsonDbCachingListModelPrivate::clearNotifications() -{ - for (int i = 0; i < partitionObjects.count(); i++) - clearNotification(i); -} - -void JsonDbCachingListModelPrivate::createOrUpdateNotification(int index) -{ - Q_Q(JsonDbCachingListModel); - if (index >= partitionObjects.count()) - return; - clearNotification(index); - if (partitionObjects[index]->state() != JsonDbPartition::Online) - return; - QJsonDbWatcher *watcher = new QJsonDbWatcher(); - watcher->setQuery(query+sortOrder); - watcher->setWatchedActions(QJsonDbWatcher::Created | QJsonDbWatcher::Updated |QJsonDbWatcher::Removed); - watcher->setPartition(partitionObjects[index]->name()); - QVariantMap::ConstIterator i = queryBindings.constBegin(); - while (i != queryBindings.constEnd()) { - watcher->bindValue(i.key(), QJsonValue::fromVariant(i.value())); - ++i; - } - QObject::connect(watcher, SIGNAL(notificationsAvailable(int)), - q, SLOT(_q_notificationsAvailable())); - QObject::connect(watcher, SIGNAL(error(QtJsonDb::QJsonDbWatcher::ErrorCode,QString)), - q, SLOT(_q_notificationError(QtJsonDb::QJsonDbWatcher::ErrorCode,QString))); - JsonDatabase::sharedConnection().addWatcher(watcher); - partitionObjectDetails[index].watcher = watcher; -} - -void JsonDbCachingListModelPrivate::createOrUpdateNotifications() -{ - for (int i = 0; i < partitionObjects.count(); i++) { - createOrUpdateNotification(i); - } -} - -void JsonDbCachingListModelPrivate::parseSortOrder() -{ - Q_Q(JsonDbCachingListModel); - QRegExp orderMatch("\\[([/\\\\[\\]])[ ]*([^\\[\\]]+)[ ]*\\]"); - if (orderMatch.indexIn(sortOrder, 0) >= 0) { - ascendingOrder = false; - if (!orderMatch.cap(1).compare(QLatin1String("/"))) - ascendingOrder = true; - indexName = orderMatch.cap(2); - } - if (!indexName.isEmpty()) { - queryForIndexSpec = QString(QLatin1String("[?_type=\"Index\"][?name=\"%1\" | propertyName=\"%1\"]")).arg(indexName); - } else { - // Set default sort order (by _uuid) - q->setSortOrder(QLatin1String("[/_uuid]")); - } -} - -void JsonDbCachingListModelPrivate::setQueryForSortKeys() -{ - // Query to retrieve the sortKeys - // TODO remove the "[= {}]" from query - queryForSortKeys = query + QLatin1String("[= { _uuid: _uuid, _indexValue: _indexValue }]"); - queryForSortKeys += sortOrder; -} - -int JsonDbCachingListModelPrivate::indexOfWatcher(QJsonDbWatcher *watcher) -{ - for (int i = 0; i < partitionObjectDetails.count(); i++) { - if (watcher == partitionObjectDetails[i].watcher) - return i; - } - return -1; -} - -QJsonObject JsonDbCachingListModelPrivate::getJsonObject(int index) -{ - if (index == lastQueriedIndex) - return lastQueriedObject; - if (index < 0 || index >= objectUuids.size()) { -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug() << "getItem" << index << "size " << objectUuids.size(); -#endif - return QJsonObject(); - } - int page = objectCache.findPage(index); - if (page == -1) { - if (!cacheMiss.contains(index)) - cacheMiss.append(index); - requestPageContaining(index); - return QJsonObject(); - } - - QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); - const QString &uuid = (begin+index).value(); - if (!objectCache.hasValueAtPage(page, uuid)) { - // The value is missing, refresh page - int startIndex = 0; int count = 0; - objectCache.dropPage(page, startIndex, count); -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug() << "getItem Refresh Page: "<<page<<"Start: "<<startIndex<< "Count:"<<count<< "State :"<<state; -#endif - if (state == JsonDbCachingListModel::Ready) { - // Start the request - createObjectRequests(startIndex, count); - } else { - requestQueue.append(QPair<int, int>(startIndex, count)); + function updateItemCallback(index, response) { + if (response) { + response.object.firstName = response.object.firstName+ "*"; + response.partition.update(response.object, updateCallback); } - if (!cacheMiss.contains(index)) - cacheMiss.append(index); - return QJsonObject(); - } - if (state == JsonDbCachingListModel::Ready) // Pre-fetch only, if in Ready state - prefetchNearbyPages(index); - QJsonObject ret = objectCache.valueAtPage(page, uuid); - lastQueriedIndex = index; - lastQueriedObject = ret; - return ret; -} - -QVariant JsonDbCachingListModelPrivate::getItem(int index) -{ - return QVariant(getJsonObject(index).toVariantMap()); -} - -QVariant JsonDbCachingListModelPrivate::getItem(int index, int role) -{ - QJsonObject obj = getJsonObject(index); - return lookupJsonProperty(obj, properties[role]).toVariant(); -} - -void JsonDbCachingListModelPrivate::queueGetCallback(int index, const QJSValue &callback) -{ - if (index < 0 || index >= objectUuids.size()) - return; - int page = objectCache.findPage(index); - if (page == -1) { - requestPageContaining(index); - getCallbacks.insert(index, callback); - return; } - callGetCallback(index, callback); -} - -void JsonDbCachingListModelPrivate::callGetCallback(int index, QJSValue callback) -{ - if (index < 0 || index >= objectUuids.size()) - return; - int page = objectCache.findPage(index); - if (page == -1) { - return; - } - QVariant object = getItem(index); - JsonDbPartition *partition = getItemPartition(index); - QJSValue result = g_declEngine->newObject(); - result.setProperty(QLatin1String("object"), g_declEngine->toScriptValue(object)); - result.setProperty(QLatin1String("partition"), g_declEngine->newQObject(partition)); - if (callback.isCallable()) { - QJSValueList args; - args << QJSValue(index) << result; - callback.call(args); - getCallbacks.remove(index); - } -} - -JsonDbPartition* JsonDbCachingListModelPrivate::getItemPartition(int index) -{ - if (index < 0 || index >= objectUuids.size()) - return 0; - QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); - int partitionIndex = (begin+index).key().partitionIndex(); - if (partitionIndex <= partitionObjects.count()) - return partitionObjects[partitionIndex]; - return 0; -} - -int JsonDbCachingListModelPrivate::indexOf(const QString &uuid) const -{ - if (!objectSortValues.contains(uuid)) - return -1; - const SortingKey &key = objectSortValues.value(uuid); - QMap<SortingKey, QString>::const_iterator begin = objectUuids.constBegin(); - QMap<SortingKey, QString>::const_iterator end = objectUuids.constEnd(); - QMap<SortingKey, QString>::const_iterator i = objectUuids.find(key); - return iterator_position(begin, end, i); -} - -void JsonDbCachingListModelPrivate::sendNotification(int partitionIndex, const QJsonObject &object, QJsonDbWatcher::Action action) -{ - if (action == QJsonDbWatcher::Created) { - addItem(object, partitionIndex); - } else if (action == QJsonDbWatcher::Removed) - deleteItem(object, partitionIndex); - else if (action == QJsonDbWatcher::Updated) { - updateItem(object, partitionIndex); + onClicked: { + contacts.get(listView.currentIndex, updateItemCallback); } -} - -void JsonDbCachingListModelPrivate::_q_keyResponse(int index, const QList<QJsonObject> &v, const QString &sortKey) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_UNUSED(sortKey) - fillKeys(v, index); -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} - -void JsonDbCachingListModelPrivate::_q_valueResponse(int index, const QList<QJsonObject> &v) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - fillData(v, index); -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} - -void JsonDbCachingListModelPrivate::_q_indexResponse(int index, const QList<QJsonObject> &v) + \endcode +*/ +void JsonDbCachingListModel::get(int index, const QJSValue &callback) { -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - verifyIndexSpec(v, index); -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif + getCallbacks.insertMulti(index, callback); + fetchObject(index); } -void JsonDbCachingListModelPrivate::_q_partitionStateChanged(JsonDbPartition::State state) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_UNUSED(state) -#ifdef JSONDB_LISTMODEL_DEBUG - qDebug() << Q_FUNC_INFO; -#endif - if (componentComplete && !query.isEmpty() && partitionsReady()) { - createOrUpdateNotifications(); - fetchModel(); - } - -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} +/*! + \qmlmethod int QtJsonDb::JsonDbCachingListModel::indexOf(string uuid) -void JsonDbCachingListModelPrivate::_q_readError(QtJsonDb::QJsonDbRequest::ErrorCode code, const QString & message) + Returns the index of the object with the \a uuid in the model. If the object is + not found it returns -1 +*/ +int JsonDbCachingListModel::indexOf(const QString &uuid) const { -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_Q(JsonDbCachingListModel); - qWarning() << QString("JsonDb error: %1 %2").arg(code).arg(message); - if (code != QtJsonDb::QJsonDbRequest::PartitionUnavailable) { - int oldErrorCode = errorCode; - errorCode = code; - errorString = message; - if (oldErrorCode != errorCode) - emit q->errorChanged(q->error()); - } -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif + return QJsonDbQueryModel::indexOf(uuid); } -void JsonDbCachingListModelPrivate::_q_notificationsAvailable() -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_Q(JsonDbCachingListModel); - QJsonDbWatcher *watcher = qobject_cast<QJsonDbWatcher *>(q->sender()); - int partitionIndex = indexOfWatcher(watcher); - if (!watcher || partitionIndex == -1) - return; - QList<QJsonDbNotification> list = watcher->takeNotifications(); - for (int i = 0; i < list.count(); i++) { - const QJsonDbNotification & notification = list[i]; - QJsonObject object = notification.object(); - QJsonDbWatcher::Action action = notification.action(); - if (state == JsonDbCachingListModel::Querying) { - NotificationItem pending; - pending.partitionIndex = partitionIndex; - pending.item = object; - pending.action = action; - pendingNotifications.append(pending); - } else { - foreach (NotificationItem pending, pendingNotifications) - sendNotification(pending.partitionIndex, pending.item, pending.action); - pendingNotifications.clear(); - sendNotification(partitionIndex, object, action); - } - } -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} +/*! + \qmlmethod object QtJsonDb::JsonDbCachingListModel::getPartition(int index) -void JsonDbCachingListModelPrivate::_q_notificationError(QtJsonDb::QJsonDbWatcher::ErrorCode code, const QString &message) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_Q(JsonDbCachingListModel); - if (code != static_cast<QtJsonDb::QJsonDbWatcher::ErrorCode>(QtJsonDb::QJsonDbRequest::PartitionUnavailable)) { - int oldErrorCode = errorCode; - errorCode = code; - errorString = message; - if (oldErrorCode != errorCode) - emit q->errorChanged(q->error()); - } -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} + Returns the partition object at the specified \a index in the model. If + the index is out of range it returns an empty object. +*/ -void JsonDbCachingListModelPrivate::_q_verifyDefaultIndexType(int index) +JsonDbPartition* JsonDbCachingListModel::getPartition(int index) { -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - SortIndexSpec &indexSpec = partitionIndexDetails[index].spec; - partitionIndexDetails[index].valid = true; - if (!indexName.compare(QLatin1String("_uuid"))) { - indexSpec.name = QLatin1String("_uuid"); - indexSpec.type = SortIndexSpec::UUID; - indexSpec.caseSensitive = false; - } else if (!indexName.compare(QLatin1String("_type"))) { - indexSpec.name = QLatin1String("_type"); - indexSpec.type = SortIndexSpec::String; - indexSpec.caseSensitive = true; + const QString partitionName = this->partitionName(index); + if (!this->nameToJsonDbPartitionMap.contains(partitionName)) { + this->nameToJsonDbPartitionMap.insert(partitionName, + new JsonDbPartition(partitionName)); } - //Check if all index specs are supported. - bool checkedAll = true; - for (int i = 0; i < partitionIndexDetails.count(); i++) { - if (!partitionIndexDetails[i].valid) { - checkedAll = false; - break; - } - } - if (checkedAll) { - //Start fetching the keys. - setQueryForSortKeys(); - for (int i = 0; i < partitionKeyRequestDetails.count(); i++) { - fetchPartitionKeys(i); - } - } -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif + return this->nameToJsonDbPartitionMap.value(partitionName); } -void JsonDbCachingListModelPrivate::initPartition(JsonDbPartition *v) -{ - Q_Q(JsonDbCachingListModel); - partitionObjects.append(QPointer<JsonDbPartition>(v)); - partitionObjectDetails.append(RequestInfo()); - ModelRequest *valueRequest = new ModelRequest(); - QObject::connect(valueRequest, SIGNAL(finished(int,QList<QJsonObject>,QString)), - q, SLOT(_q_valueResponse(int,QList<QJsonObject>))); - QObject::connect(valueRequest, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)), - q, SLOT(_q_readError(QtJsonDb::QJsonDbRequest::ErrorCode,QString))); - valueRequests.append(valueRequest); - - partitionKeyRequestDetails.append(RequestInfo()); - ModelRequest *keyRequest = new ModelRequest(); - QObject::connect(keyRequest, SIGNAL(finished(int,QList<QJsonObject>,QString)), - q, SLOT(_q_keyResponse(int,QList<QJsonObject>,QString))); - QObject::connect(keyRequest, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)), - q, SLOT(_q_readError(QtJsonDb::QJsonDbRequest::ErrorCode,QString))); - keyRequests.append(keyRequest); - - partitionObjectUuids.append(JsonDbModelIndexType()); - - partitionIndexDetails.append(IndexInfo()); - ModelRequest *indexRequest = new ModelRequest(); - QObject::connect(indexRequest, SIGNAL(finished(int,QList<QJsonObject>,QString)), - q, SLOT(_q_indexResponse(int,QList<QJsonObject>))); - QObject::connect(indexRequest, SIGNAL(error(QtJsonDb::QJsonDbRequest::ErrorCode,QString)), - q, SLOT(_q_readError(QtJsonDb::QJsonDbRequest::ErrorCode,QString))); - indexRequests.append(indexRequest); - - if (componentComplete && !query.isEmpty()) { - parseSortOrder(); - createOrUpdateNotification(partitionObjects.count()-1); - if (state == JsonDbCachingListModel::None) - resetModel = true; - fetchIndexSpec(partitionObjects.count()-1); - } -} +//---------------- PROPERTIES------------------------------ -void JsonDbCachingListModelPrivate::appendPartition(JsonDbPartition *v) -{ - Q_Q(JsonDbCachingListModel); - QObject::connect (v, SIGNAL(stateChanged(JsonDbPartition::State)), q, SLOT(_q_partitionStateChanged(JsonDbPartition::State))); - initPartition (v); -} - -void JsonDbCachingListModelPrivate::partitions_append(QQmlListProperty<JsonDbPartition> *p, JsonDbPartition *v) -{ - JsonDbCachingListModel *q = qobject_cast<JsonDbCachingListModel *>(p->object); - JsonDbCachingListModelPrivate *pThis = (q) ? q->d_func() : 0; - if (!v) { - qWarning("Invalid partition object"); - return; - } - - if (pThis) { - pThis->appendPartition(v); - } -} - -int JsonDbCachingListModelPrivate::partitions_count(QQmlListProperty<JsonDbPartition> *p) -{ - JsonDbCachingListModel *q = qobject_cast<JsonDbCachingListModel *>(p->object); - JsonDbCachingListModelPrivate *pThis = (q) ? q->d_func() : 0; - if (pThis) { - return pThis->partitionObjects.count(); - } - return 0; -} +/*! + \qmlproperty object QtJsonDb::JsonDbCachingListModel::bindings + Holds the bindings for the placeholders used in the query string. Note that + the placeholder marker '%' should not be included as part of the keys. -JsonDbPartition* JsonDbCachingListModelPrivate::partitions_at(QQmlListProperty<JsonDbPartition> *p, int idx) -{ - JsonDbCachingListModel *q = qobject_cast<JsonDbCachingListModel *>(p->object); - JsonDbCachingListModelPrivate *pThis = (q) ? q->d_func() : 0; - if (pThis && idx < pThis->partitionObjects.count()) { - return pThis->partitionObjects.at(idx); + \qml + JsonDb.JsonDbCachingListModel { + id: listModel + query: '[?_type="Contact"][?name=%firstName]' + bindings :{'firstName':'Book'} + partitions:[ JsonDb.Partition { + name:"com.nokia.shared" + }] } - return 0; -} + \endqml -void JsonDbCachingListModelPrivate::clearPartitions() -{ - partitionObjects.clear(); - partitionObjectDetails.clear(); - partitionKeyRequestDetails.clear(); - partitionObjectUuids.clear(); - partitionIndexDetails.clear(); - while (!keyRequests.isEmpty()) { - delete keyRequests[0]; - keyRequests.removeFirst(); - } - while (!indexRequests.isEmpty()) { - delete indexRequests[0]; - indexRequests.removeFirst(); - } - while (!valueRequests.isEmpty()) { - delete valueRequests[0]; - valueRequests.removeFirst(); - } - reset(); -} + \sa QtJsonDb::JsonDbCachingListModel::query -void JsonDbCachingListModelPrivate::partitions_clear(QQmlListProperty<JsonDbPartition> *p) -{ - JsonDbCachingListModel *q = qobject_cast<JsonDbCachingListModel *>(p->object); - JsonDbCachingListModelPrivate *pThis = (q) ? q->d_func() : 0; - if (pThis) { - pThis->clearPartitions(); - } -} +*/ /*! - \qmlclass JsonDbCachingListModel JsonDbCachingListModel - \inqmlmodule QtJsonDb 1.0 - \since 1.0 + \qmlproperty int QtJsonDb::JsonDbCachingListModel::cacheSize + Holds the maximum number of items cached by the model. +*/ - The JsonDbCachingListModel provides a read-only ListModel usable with views such as - ListView or GridView displaying data items matching a query. The sorting is done using - an index set on the JsonDb server. If it doesn't find a matching index for the sortkey, - the model goes into Error state. Maximum number of items in the model cache can be set - by cacheSize property. +/*! + \qmlproperty object QtJsonDb::JsonDbCachingListModel::error + \readonly - When an item is not present in the internal cache, the model can return an 'undefined' - object from data() method. It will be queued for retrieval and the model will notify its - presence using the dataChanged() signal. + This property holds the current error information for the object. It contains: + \list + \li error.code - code for the current error. + \li error.message - detailed explanation of the error + \endlist +*/ - The model is initialized by retrieving the result in chunks. After receiving the first - chunk, the model is reset with items from it. The state will be "Querying" during - fetching data and will be changed to "Ready". +/*! + \qmlproperty string QtJsonDb::JsonDbCachingListModel::query - \note This is still a work in progress, so expect minor changes. + The query string in JsonQuery format used by the model to fetch + items from the database. Setting an empty query clears all the elements - \code - import QtJsonDb 1.0 as JsonDb + In the following example, the JsonDbCachingListModel would contain all + the objects with \a _type "CONTACT" from partition called "com.nokia.shared" + \qml JsonDb.JsonDbCachingListModel { - id: contactsModel - query: '[?_type="Contact"]' - cacheSize: 100 - partitions: [JsonDb.Partition { + id: listModel + query: "[?_type=\"CONTACT\"]" + partitions:[ JsonDb.Partition { name:"com.nokia.shared" }] - sortOrder: "[/firstName]" - roleNames: ["firstName", "lastName", "phoneNumber"] - } - ListView { - model: contactsModel - Row { - spacing: 10 - Text { - text: firstName + " " + lastName - } - Text { - text: phoneNumber - } - } } - \endcode -*/ - -JsonDbCachingListModel::JsonDbCachingListModel(QObject *parent) - : QAbstractListModel(parent) - , d_ptr(new JsonDbCachingListModelPrivate(this)) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_D(JsonDbCachingListModel); - d->init(); -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} - -JsonDbCachingListModel::~JsonDbCachingListModel() -{ -} - -void JsonDbCachingListModel::classBegin() -{ -} + \endqml -void JsonDbCachingListModel::componentComplete() -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_D(JsonDbCachingListModel); - d->componentComplete = true; - if (!d->query.isEmpty() && d->partitionObjects.count() && d->partitionsReady()) { - d->createOrUpdateNotifications(); - d->fetchModel(); - } -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} +*/ /*! \qmlproperty int QtJsonDb1::JsonDbCachingListModel::rowCount The number of items in the model. */ -int JsonDbCachingListModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - Q_D(const JsonDbCachingListModel); - return d->objectUuids.count(); -} - -QVariant JsonDbCachingListModel::data(const QModelIndex &modelIndex, int role) const -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - JsonDbCachingListModel *pThis = const_cast<JsonDbCachingListModel *>(this); - QVariant ret = pThis->d_func()->getItem(modelIndex.row(), role); -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif - return ret; -} - -QHash<int, QByteArray> JsonDbCachingListModel::roleNames() const -{ - Q_D(const JsonDbCachingListModel); - return d->roleNames; -} /*! \qmlproperty ListOrObject QtJsonDb1::JsonDbCachingListModel::roleNames @@ -1418,55 +296,15 @@ QHash<int, QByteArray> JsonDbCachingListModel::roleNames() const \endcode */ -QVariant JsonDbCachingListModel::scriptableRoleNames() const -{ - Q_D(const JsonDbCachingListModel); - return d->roleMap; -} - -void JsonDbCachingListModel::setScriptableRoleNames(const QVariant &vroles) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_D(JsonDbCachingListModel); - d->properties.clear(); - d->roleNames.clear(); - if (vroles.type() == QVariant::Map) { - QVariantMap roles = vroles.toMap(); - d->roleMap = roles; - int i = 0; - for (QVariantMap::const_iterator it = roles.begin(); it != roles.end(); ++it) { - d->roleNames.insert(i, it.key().toLatin1()); - d->properties.insert(i, removeArrayOperator(it.value().toString()).split('.')); - i++; - } - } else { - QVariantList roleList = vroles.toList(); - d->roleMap.clear(); - for (int i = 0; i < roleList.size(); i++) { - QString role = roleList[i].toString(); - d->roleMap[role] = role; - d->roleNames.insert(i, role.toLatin1()); - d->properties.insert(i, removeArrayOperator(role).split('.')); - } - } -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} - /*! - \qmlproperty string QtJsonDb1::JsonDbCachingListModel::query + \qmlproperty string QtJsonDb::JsonDbCachingListModel::sortOrder - The query string in JsonQuery format used by the model to fetch - items from the database. Setting an empty query clears all the elements + The order used by the model to sort the items. Make sure that there + is a matching Index in the database for this sortOrder. This has to be + specified in the JsonQuery format. In the following example, the JsonDbCachingListModel would contain all - the objects with \a _type "CONTACT" from partition called "com.nokia.shared" + the objects of type \a "CONTACT" sorted by their \a firstName field \qml JsonDb.JsonDbCachingListModel { @@ -1475,162 +313,25 @@ void JsonDbCachingListModel::setScriptableRoleNames(const QVariant &vroles) partitions:[ JsonDb.Partition { name:"com.nokia.shared" }] + sortOrder: "[/firstName]" } \endqml \sa QtJsonDb1::JsonDbCachingListModel::bindings */ -QString JsonDbCachingListModel::query() const -{ - Q_D(const JsonDbCachingListModel); - return d->query; -} - -void JsonDbCachingListModel::setQuery(const QString &newQuery) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_D(JsonDbCachingListModel); - - const QString oldQuery = d->query; - if (oldQuery == newQuery) - return; - - d->query = newQuery; - if (rowCount() && d->query.isEmpty()) { - d->reset(); - } - - if (!d->componentComplete || d->query.isEmpty() || !d->partitionObjects.count() || !d->partitionsReady()) - return; - d->createOrUpdateNotifications(); - d->fetchModel(); -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} - -/*! - \qmlproperty object QtJsonDb1::JsonDbCachingListModel::bindings - Holds the bindings for the placeholders used in the query string. Note that - the placeholder marker '%' should not be included as part of the keys. - - \qml - JsonDb.JsonDbCachingListModel { - id: listModel - query: '[?_type="Contact"][?name=%firstName]' - bindings :{'firstName':'Book'} - partitions:[ JsonDb.Partition { - name:"com.nokia.shared" - }] - } - \endqml - - \sa QtJsonDb1::JsonDbCachingListModel::query - -*/ - -QVariantMap JsonDbCachingListModel::bindings() const -{ - Q_D(const JsonDbCachingListModel); - return d->queryBindings; -} - -void JsonDbCachingListModel::setBindings(const QVariantMap &newBindings) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_D(JsonDbCachingListModel); - d->queryBindings = newBindings; - - if (!d->componentComplete || d->query.isEmpty() || !d->partitionObjects.count() || !d->partitionsReady()) - return; - d->createOrUpdateNotifications(); - d->fetchModel(); -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} /*! - \qmlproperty int QtJsonDb1::JsonDbCachingListModel::cacheSize - Holds the maximum number of items cached by the model. - - \code - JsonDb.JsonDbCachingListModel { - id: listModel - query: "[?_type=\"CONTACT\"]" - cacheSize: 100 - partitions: [JsonDb.Partition { - name:"com.nokia.shared" - }] - } - \endcode - + \qmlproperty Status QtJsonDb::JsonDbCachingListModel::status + The current status of the model. + \list + \li Status.None - The model is not initialized + \li Status.Querying - It is querying the results from server + \li Status.Ready - Results are ready + \li Status.Error - Cannot find a matching index on the server + \endlist */ -int JsonDbCachingListModel::cacheSize() const -{ - Q_D(const JsonDbCachingListModel); - return d->cacheSize; -} - -void JsonDbCachingListModel::setCacheSize(int newCacheSize) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_D(JsonDbCachingListModel); - if (newCacheSize == d->cacheSize) - return; - - d->cacheSize = newCacheSize; - d->setCacheParams(d->cacheSize); - if (!d->componentComplete || d->query.isEmpty() || !d->partitionObjects.count() || !d->partitionsReady()) - return; - - d->fetchModel(); -#ifdef JSONDB_LISTMODEL_DEBUG - d->objectCache.dumpCacheDetails(); -#endif -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} - -void JsonDbCachingListModel::partitionNameChanged(const QString &partitionName) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_UNUSED(partitionName); - Q_D(JsonDbCachingListModel); - - if (!d->componentComplete || d->query.isEmpty() || !d->partitionObjects.count() || !d->partitionsReady()) - return; - - d->createOrUpdateNotifications(); - d->fetchModel(); -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif -} - /*! \qmlproperty list QtJsonDb1::JsonDbCachingListModel::partitions Holds the list of partition objects for the model. @@ -1646,175 +347,84 @@ void JsonDbCachingListModel::partitionNameChanged(const QString &partitionName) \endcode */ - QQmlListProperty<JsonDbPartition> JsonDbCachingListModel::partitions() { return QQmlListProperty<JsonDbPartition>(this, 0 - , &JsonDbCachingListModelPrivate::partitions_append - , &JsonDbCachingListModelPrivate::partitions_count - , &JsonDbCachingListModelPrivate::partitions_at - , &JsonDbCachingListModelPrivate::partitions_clear); + , &partitions_append + , &partitions_count + , &partitions_at + , &partitions_clear); } -/*! - \qmlproperty string QtJsonDb1::JsonDbCachingListModel::sortOrder - - The order used by the model to sort the items. Make sure that there - is a matching Index in the database for this sortOrder. This has to be - specified in the JsonQuery format. - - In the following example, the JsonDbCachingListModel would contain all - the objects of type \a "CONTACT" sorted by their \a firstName field - - \qml - JsonDb.JsonDbCachingListModel { - id: listModel - query: "[?_type=\"CONTACT\"]" - partitions: [ JsonDb.Partition { - name:"com.nokia.shared" - }] - sortOrder: "[/firstName]" - } - \endqml - -*/ - -QString JsonDbCachingListModel::sortOrder() const +void JsonDbCachingListModel::partitions_append(QQmlListProperty<JsonDbPartition> *p, JsonDbPartition *v) { - Q_D(const JsonDbCachingListModel); - return d->sortOrder; + if (!v) { + qWarning("Invalid partition object"); + return; + } + JsonDbCachingListModel *q = qobject_cast<JsonDbCachingListModel *>(p->object); + if (q) { + const QString partitionName = v->name(); + q->appendPartitionName(partitionName); + if (!q->nameToJsonDbPartitionMap.contains(partitionName)) { + q->nameToJsonDbPartitionMap.insert(partitionName, + new JsonDbPartition(partitionName)); + } + } } -void JsonDbCachingListModel::setSortOrder(const QString &newSortOrder) +int JsonDbCachingListModel::partitions_count(QQmlListProperty<JsonDbPartition> *p) { -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_D(JsonDbCachingListModel); - - const QString oldSortOrder = d->sortOrder; - d->sortOrder = newSortOrder; - if (oldSortOrder != newSortOrder) { - d->parseSortOrder(); - if (!d->componentComplete || d->query.isEmpty() || !d->partitionObjects.count() || !d->partitionsReady()) - return; - d->createOrUpdateNotifications(); - d->fetchModel(); + JsonDbCachingListModel *q = qobject_cast<JsonDbCachingListModel *>(p->object); + if (q) { + return q->partitionNames().size(); } -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif + return 0; } -/*! - \qmlproperty State QtJsonDb1::JsonDbCachingListModel::state - The current state of the model. - \list - \li State.None - The model is not initialized - \li State.Querying - It is querying the results from server - \li State.Ready - Results are ready - \li State.Error - Cannot find a matching index on the server - \endlist -*/ - -JsonDbCachingListModel::State JsonDbCachingListModel::state() const +JsonDbPartition* JsonDbCachingListModel::partitions_at(QQmlListProperty<JsonDbPartition> *p, int idx) { - Q_D(const JsonDbCachingListModel); - return d->state; + JsonDbCachingListModel *q = qobject_cast<JsonDbCachingListModel *>(p->object); + if (q && idx < q->partitionNames().size()) { + QString partitionName = q->partitionName(idx); + if (q->nameToJsonDbPartitionMap.contains(partitionName)) + return q->nameToJsonDbPartitionMap.value(partitionName); + } + return 0; } -/*! - \qmlmethod int QtJsonDb1::JsonDbCachingListModel::indexOf(string uuid) - - Returns the index of the object with the \a uuid in the model. If the object is - not found it returns -1 -*/ -int JsonDbCachingListModel::indexOf(const QString &uuid) const +void JsonDbCachingListModel::partitions_clear(QQmlListProperty<JsonDbPartition> *p) { - Q_D(const JsonDbCachingListModel); - return d->indexOf(uuid); -} - -/*! - \qmlmethod QtJsonDb1::JsonDbCachingListModel::get(int index, function callback) - - Calls the callback with object at the specified \a index in the model. The result.object property - contains the object in its raw form as returned by the query, the rolenames - are not applied. The object.partition is the partition for the returned. - If the index is out of range it returns an object with empty partition & object properties. - - \code - function updateCallback(error, response) { - if (error) { - console.log("Update Error :"+JSON.stringify(error)); - return, - } - console.log("Response from Update"); - console.log("response.id = "+response.id +" count = "+response.items.length); - for (var i = 0; i < response.items.length; i++) { - console.log("_uuid = "+response.items[i]._uuid +" ._version = "+response.items[i]._version); - } - } - function updateItemCallback(index, response) { - if (response) { - response.object.firstName = response.object.firstName+ "*"; - response.partition.update(response.object, updateCallback); + JsonDbCachingListModel *q = qobject_cast<JsonDbCachingListModel *>(p->object); + if (q) { + q->setPartitionNames(QStringList()); + foreach (const QString &partitionName, q->nameToJsonDbPartitionMap.keys()) { + q->nameToJsonDbPartitionMap.take(partitionName)->deleteLater(); } } - onClicked: { - contacts.get(listView.currentIndex, updateItemCallback); - } - \endcode -*/ -void JsonDbCachingListModel::get(int index, const QJSValue &callback) -{ -#ifdef JSONDB_LISTMODEL_BENCHMARK - QElapsedTimer elt; - elt.start(); -#endif - Q_D(JsonDbCachingListModel); - d->queueGetCallback(index, callback); -#ifdef JSONDB_LISTMODEL_BENCHMARK - qint64 elap = elt.elapsed(); - if (elap > 3) - qDebug() << Q_FUNC_INFO << "took more than 3 ms (" << elap << "ms )"; -#endif } -/*! - \qmlmethod object QtJsonDb1::JsonDbCachingListModel::getPartition(int index) - - Returns the partition object at the specified \a index in the model. If - the index is out of range it returns an empty object. -*/ - -JsonDbPartition* JsonDbCachingListModel::getPartition(int index) const +void JsonDbCachingListModel::onObjectAvailable(int index, QJsonObject availableObject, QString partitionName) { - JsonDbCachingListModel *pThis = const_cast<JsonDbCachingListModel *>(this); - return pThis->d_func()->getItemPartition(index); -} + if (getCallbacks.contains(index)) { + QVariant object = QVariant (availableObject.toVariantMap()); + JsonDbPartition *partition = new JsonDbPartition(partitionName); + QJSValue result = g_declEngine->newObject(); + result.setProperty(QLatin1String("object"), g_declEngine->toScriptValue(object)); + result.setProperty(QLatin1String("partition"), g_declEngine->newQObject(partition)); -/*! - \qmlproperty object QtJsonDb1::JsonDbCachingListModel::error - \readonly + QMap<int, QJSValue>::iterator callbacksIter = getCallbacks.find(index); - This property holds the current error information for the object. It contains: - \list - \li error.code - code for the current error. - \li error.message - detailed explanation of the error - \endlist -*/ - -QVariantMap JsonDbCachingListModel::error() const -{ - Q_D(const JsonDbCachingListModel); - QVariantMap errorMap; - errorMap.insert(QLatin1String("code"), d->errorCode); - errorMap.insert(QLatin1String("message"), d->errorString); - return errorMap; + while ((callbacksIter != getCallbacks.end()) && (callbacksIter.key() == index)) { + if (callbacksIter.value().isCallable()) { + QJSValueList args; + args << QJSValue(index) << result; + callbacksIter.value().call(args); + } + callbacksIter ++; + } + getCallbacks.remove(index); + } } #include "moc_jsondbcachinglistmodel.cpp" diff --git a/src/imports/jsondb/jsondbcachinglistmodel.h b/src/imports/jsondb/jsondbcachinglistmodel.h index 68546c8..4173f17 100644 --- a/src/imports/jsondb/jsondbcachinglistmodel.h +++ b/src/imports/jsondb/jsondbcachinglistmodel.h @@ -39,103 +39,53 @@ ** ****************************************************************************/ -#ifndef JSONDBCACHINGLISTMODEL_H -#define JSONDBCACHINGLISTMODEL_H - -#include <QAbstractListModel> -#include <QHash> -#include <QMultiMap> -#include <QSet> -#include <QSharedDataPointer> -#include <QStringList> +#ifndef JSONDBCACHINGLISTMODEL_QML_H +#define JSONDBCACHINGLISTMODEL_QML_H + #include <QQmlParserStatus> #include <QQmlListProperty> #include <QJSValue> -#include <QScopedPointer> #include "jsondbpartition.h" +#include <private/qjsondbquerymodel_p.h> QT_BEGIN_NAMESPACE_JSONDB -class JsonDbCachingListModelPrivate; -class JsonDbPartition; -class JsonDbCachingListModel : public QAbstractListModel, public QQmlParserStatus +class JsonDbCachingListModel : public QJsonDbQueryModel, public QQmlParserStatus { Q_OBJECT Q_INTERFACES(QQmlParserStatus) - Q_ENUMS(State) + public: - enum State { None, Querying, Ready, Error }; JsonDbCachingListModel(QObject *parent = 0); virtual ~JsonDbCachingListModel(); - Q_PROPERTY(State state READ state NOTIFY stateChanged) - Q_PROPERTY(QString query READ query WRITE setQuery) - Q_PROPERTY(QVariantMap bindings READ bindings WRITE setBindings) - Q_PROPERTY(QString sortOrder READ sortOrder WRITE setSortOrder) - Q_PROPERTY(int rowCount READ rowCount NOTIFY rowCountChanged) - Q_PROPERTY(QVariant roleNames READ scriptableRoleNames WRITE setScriptableRoleNames) - Q_PROPERTY(int cacheSize READ cacheSize WRITE setCacheSize) Q_PROPERTY(QQmlListProperty<JsonDbPartition> partitions READ partitions) - Q_PROPERTY(QVariantMap error READ error NOTIFY errorChanged) virtual void classBegin(); virtual void componentComplete(); - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual QHash<int,QByteArray> roleNames() const; - - QVariant scriptableRoleNames() const; - void setScriptableRoleNames(const QVariant &roles); - - QString query() const; - void setQuery(const QString &newQuery); - - QVariantMap bindings() const; - void setBindings(const QVariantMap &newBindings); - QQmlListProperty<JsonDbPartition> partitions(); - int cacheSize() const; - void setCacheSize(int newCacheSize); - - QString sortOrder() const; - void setSortOrder(const QString &newSortOrder); - - - JsonDbCachingListModel::State state() const; - - Q_INVOKABLE int indexOf(const QString &uuid) const; Q_INVOKABLE void get(int index, const QJSValue &callback); - Q_INVOKABLE JsonDbPartition* getPartition(int index) const; - QVariantMap error() const; - -signals: - void stateChanged(State state) const; - void rowCountChanged(int newCount) const; - void errorChanged(QVariantMap newError); - -private Q_SLOTS: - void partitionNameChanged(const QString &partitionName); + Q_INVOKABLE JsonDbPartition* getPartition(int index); + Q_INVOKABLE int indexOf(const QString &uuid) const; + static void partitions_append(QQmlListProperty<JsonDbPartition> *p, JsonDbPartition *v); + static int partitions_count(QQmlListProperty<JsonDbPartition> *p); + static JsonDbPartition* partitions_at(QQmlListProperty<JsonDbPartition> *p, int idx); + static void partitions_clear(QQmlListProperty<JsonDbPartition> *p); private: Q_DISABLE_COPY(JsonDbCachingListModel) - Q_DECLARE_PRIVATE(JsonDbCachingListModel) - QScopedPointer<JsonDbCachingListModelPrivate> d_ptr; - - Q_PRIVATE_SLOT(d_func(), void _q_verifyDefaultIndexType(int)) - Q_PRIVATE_SLOT(d_func(), void _q_notificationsAvailable()) - Q_PRIVATE_SLOT(d_func(), void _q_notificationError(QtJsonDb::QJsonDbWatcher::ErrorCode, QString)) - Q_PRIVATE_SLOT(d_func(), void _q_keyResponse(int, QList<QJsonObject>, QString)) - Q_PRIVATE_SLOT(d_func(), void _q_valueResponse(int, QList<QJsonObject>)) - Q_PRIVATE_SLOT(d_func(), void _q_indexResponse(int, QList<QJsonObject>)) - Q_PRIVATE_SLOT(d_func(), void _q_readError(QtJsonDb::QJsonDbRequest::ErrorCode, QString)) - Q_PRIVATE_SLOT(d_func(), void _q_partitionStateChanged(JsonDbPartition::State)) + + QMap <int, QJSValue> getCallbacks; + QMap <QString, JsonDbPartition *> nameToJsonDbPartitionMap; +private slots: + void onObjectAvailable(int index, QJsonObject availableObject, QString partitionName); }; QT_END_NAMESPACE_JSONDB -#endif // JSONDBCACHINGLISTMODEL_H +#endif // JSONDBCACHINGLISTMODEL_QML_H diff --git a/src/imports/jsondb/jsondblistmodel.cpp b/src/imports/jsondb/jsondblistmodel.cpp index 9ed0ed4..1bdce01 100644 --- a/src/imports/jsondb/jsondblistmodel.cpp +++ b/src/imports/jsondb/jsondblistmodel.cpp @@ -49,7 +49,6 @@ #include <QString> #include <qdebug.h> - #undef DEBUG_LIST_MODEL #ifdef DEBUG_LIST_MODEL diff --git a/src/imports/jsondb/jsondblistmodel_p.h b/src/imports/jsondb/jsondblistmodel_p.h index 7852fa4..616d4b4 100644 --- a/src/imports/jsondb/jsondblistmodel_p.h +++ b/src/imports/jsondb/jsondblistmodel_p.h @@ -53,10 +53,16 @@ #include <QJsonDbWatcher> #include <QJsonDbReadRequest> #include <QJsonDbUpdateRequest> -#include "jsondbmodelutils.h" +#include <private/qjsondbmodelutils_p.h> QT_BEGIN_NAMESPACE_JSONDB +struct CallbackInfo { + int index; + QJSValue successCallback; + QJSValue errorCallback; +}; + class JsonDbListModelPrivate { Q_DECLARE_PUBLIC(JsonDbListModel) diff --git a/src/imports/jsondb/jsondbpartition.cpp b/src/imports/jsondb/jsondbpartition.cpp index 79b9c03..7ff1ad1 100644 --- a/src/imports/jsondb/jsondbpartition.cpp +++ b/src/imports/jsondb/jsondbpartition.cpp @@ -38,6 +38,9 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ +#include <private/qjsondbmodelutils_p.h> +#include <QJsonDbCreateRequest> + #include "jsondbpartition.h" #include "jsondatabase.h" #include "jsondbnotification.h" @@ -49,7 +52,6 @@ #include <qdebug.h> QT_BEGIN_NAMESPACE_JSONDB - /*! \qmlclass Partition JsonDbPartition \inqmlmodule QtJsonDb 1.0 @@ -164,26 +166,6 @@ static QVariant qjsvalue_to_qvariant(const QJSValue &value) } } -QList<QJsonObject> qvariantlist_to_qjsonobject_list(const QVariantList &list) -{ - QList<QJsonObject> objects; - int count = list.count(); - for (int i = 0; i < count; i++) { - objects.append(QJsonObject::fromVariantMap(list[i].toMap())); - } - return objects; -} - -QVariantList qjsonobject_list_to_qvariantlist(const QList<QJsonObject> &list) -{ - QVariantList objects; - int count = list.count(); - for (int i = 0; i < count; i++) { - objects.append(list[i].toVariantMap()); - } - return objects; -} - QJSValue qjsonobject_list_to_qjsvalue(const QList<QJsonObject> &list) { int count = list.count(); diff --git a/src/imports/jsondb/jsondbpartition.h b/src/imports/jsondb/jsondbpartition.h index 8aeca06..e732fea 100644 --- a/src/imports/jsondb/jsondbpartition.h +++ b/src/imports/jsondb/jsondbpartition.h @@ -54,8 +54,6 @@ #include <QJsonDbWatcher> QT_BEGIN_NAMESPACE_JSONDB - -class JsonDatabase; class JsonDbNotify; class JsonDbPartitionPrivate; class JsonDbQueryObject; @@ -141,11 +139,12 @@ private Q_SLOTS: void notificationsAvailable(); void notificationError(QtJsonDb::QJsonDbWatcher::ErrorCode code, const QString &message); - friend class JsonDatabase; friend class JsonDbNotify; friend class JsonDbQueryObject; }; +QJSValue qjsonobject_list_to_qjsvalue(const QList<QJsonObject> &list); + QT_END_NAMESPACE_JSONDB #endif diff --git a/src/imports/jsondb/jsondbqueryobject.cpp b/src/imports/jsondb/jsondbqueryobject.cpp index ac50a49..02d0133 100644 --- a/src/imports/jsondb/jsondbqueryobject.cpp +++ b/src/imports/jsondb/jsondbqueryobject.cpp @@ -43,7 +43,7 @@ #include "jsondatabase.h" #include "plugin.h" #include <private/qjsondbstrings_p.h> -#include <jsondbmodelutils.h> +#include <private/qjsondbmodelutils_p.h> #include <qdebug.h> QT_BEGIN_NAMESPACE_JSONDB diff --git a/src/imports/jsondb/jsondbsortinglistmodel_p.h b/src/imports/jsondb/jsondbsortinglistmodel_p.h index b109bc5..345baa4 100644 --- a/src/imports/jsondb/jsondbsortinglistmodel_p.h +++ b/src/imports/jsondb/jsondbsortinglistmodel_p.h @@ -51,8 +51,7 @@ #include <QUuid> #include <QJSEngine> #include <QJSValueIterator> - -#include "jsondbmodelutils.h" +#include <private/qjsondbmodelutils_p.h> QT_BEGIN_NAMESPACE_JSONDB diff --git a/tests/auto/jsondbcachinglistmodel/jsondbcachinglistmodel.pro b/tests/auto/jsondbcachinglistmodel/jsondbcachinglistmodel.pro index c054dd4..fd7fbb1 100644 --- a/tests/auto/jsondbcachinglistmodel/jsondbcachinglistmodel.pro +++ b/tests/auto/jsondbcachinglistmodel/jsondbcachinglistmodel.pro @@ -3,7 +3,7 @@ TARGET = tst_jsondbcachinglistmodel DEPENDPATH += . INCLUDEPATH += . ../../shared/ -QT = core network testlib gui qml jsondb +QT = core network testlib gui qml jsondb-private CONFIG -= app_bundle CONFIG += testcase diff --git a/tests/auto/jsondbcachinglistmodel/testjsondbcachinglistmodel.cpp b/tests/auto/jsondbcachinglistmodel/testjsondbcachinglistmodel.cpp index 5a50c93..fe2f829 100644 --- a/tests/auto/jsondbcachinglistmodel/testjsondbcachinglistmodel.cpp +++ b/tests/auto/jsondbcachinglistmodel/testjsondbcachinglistmodel.cpp @@ -42,6 +42,7 @@ #include <QtTest/QtTest> #include <QJSEngine> #include "testjsondbcachinglistmodel.h" +#include "private/qjsondbquerymodel_p.h" #include "../../shared/util.h" #include <QQmlListReference> @@ -1570,6 +1571,44 @@ void TestJsonDbCachingListModel::getItemNotInCache() deleteModel(listModel); } +void TestJsonDbCachingListModel::useDataToGetUuidAndIndexValue() +{ + resetWaitFlags(); + + createIndex("anotherNumber", "number"); + + for (int i=0; i < 1000; i++) { + QVariantMap item; + item.insert("_type", __FUNCTION__); + item.insert("anotherNumber", i); + int id = i%2 ? create(item, "com.nokia.shared.1") : create(item, "com.nokia.shared.2"); + waitForResponse1(id); + } + + QJsonDbQueryModel *queryModel = new QJsonDbQueryModel(QJsonDbConnection::defaultConnection(), this); + queryModel->setSortOrder(QString("[/anotherNumber]")); + queryModel->setQuery(QString("[?_type=\"%1\"]").arg(__FUNCTION__)); + queryModel->setPartitionNames(QStringList() << "com.nokia.shared.1" << "com.nokia.shared.2"); + queryModel->setQueryRoleNames(QStringList() << "_type" << "_uuid" << "_indexValue" << "anotherNumber"); + connectListModel(queryModel); + + queryModel->populate(); + + mWaitingForReset = true; + waitForExitOrTimeout(); + QCOMPARE(mWaitingForReset, false); + + QCOMPARE(queryModel->rowCount(), 1000); + + for (int i=0; i < 1000; i++) { + QVariant iv = queryModel->data (i, 1); + iv = queryModel->data (i, 2); + QCOMPARE(iv.toInt(), i); + } + + delete queryModel; +} + QStringList TestJsonDbCachingListModel::getOrderValues(QAbstractListModel *listModel) { QStringList vals; diff --git a/tests/auto/jsondbcachinglistmodel/testjsondbcachinglistmodel.h b/tests/auto/jsondbcachinglistmodel/testjsondbcachinglistmodel.h index c835b8c..f0e39a1 100644 --- a/tests/auto/jsondbcachinglistmodel/testjsondbcachinglistmodel.h +++ b/tests/auto/jsondbcachinglistmodel/testjsondbcachinglistmodel.h @@ -102,6 +102,7 @@ private slots: void indexOfUuid(); void roleNames(); void getItemNotInCache(); + void useDataToGetUuidAndIndexValue(); public: void timeout(); private: |