File indexing completed on 2024-05-12 05:26:01

0001 /*
0002  * Copyright (C) 2014 Christian Mollekopf <chrigi_1@fastmail.fm>
0003  *
0004  * This library is free software; you can redistribute it and/or
0005  * modify it under the terms of the GNU Lesser General Public
0006  * License as published by the Free Software Foundation; either
0007  * version 2.1 of the License, or (at your option) version 3, or any
0008  * later version accepted by the membership of KDE e.V. (or its
0009  * successor approved by the membership of KDE e.V.), which shall
0010  * act as a proxy defined in Section 6 of version 3 of the license.
0011  *
0012  * This library is distributed in the hope that it will be useful,
0013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  * Lesser General Public License for more details.
0016  *
0017  * You should have received a copy of the GNU Lesser General Public
0018  * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0019  */
0020 #include "modelresult.h"
0021 
0022 #include <QDebug>
0023 #include <QThread>
0024 #include <QPointer>
0025 
0026 #include "log.h"
0027 #include "notifier.h"
0028 #include "notification.h"
0029 
0030 using namespace Sink;
0031 
0032 static uint getInternalIdentifer(const QByteArray &resourceId, const QByteArray &entityId)
0033 {
0034     return qHash(resourceId + entityId);
0035 }
0036 
0037 static uint qHash(const Sink::ApplicationDomain::ApplicationDomainType &type)
0038 {
0039     Q_ASSERT(!type.identifier().isEmpty());
0040     return getInternalIdentifer(type.resourceInstanceIdentifier(), type.identifier());
0041 }
0042 
0043 static qint64 getIdentifier(const QModelIndex &idx)
0044 {
0045     if (!idx.isValid()) {
0046         return 0;
0047     }
0048     return idx.internalId();
0049 }
0050 
0051 template <class T, class Ptr>
0052 ModelResult<T, Ptr>::ModelResult(const Sink::Query &query, const QList<QByteArray> &propertyColumns, const Sink::Log::Context &ctx)
0053     : QAbstractItemModel(), mLogCtx(ctx.subContext("modelresult")), mPropertyColumns(propertyColumns), mQuery(query)
0054 {
0055     if (query.flags().testFlag(Sink::Query::UpdateStatus)) {
0056         Sink::Query resourceQuery;
0057         resourceQuery.setFilter(query.getResourceFilter());
0058         mNotifier.reset(new Sink::Notifier{resourceQuery});
0059         mNotifier->registerHandler([this](const Notification &notification) {
0060             switch (notification.type) {
0061                 case Notification::Status:
0062                 case Notification::Warning:
0063                 case Notification::Error:
0064                 case Notification::Info:
0065                 case Notification::Progress:
0066                     //These are the notifications we care about
0067                     break;
0068                 default:
0069                     //We're not interested
0070                     return;
0071             }
0072             if (notification.resource.isEmpty() || notification.entities.isEmpty()) {
0073                 return;
0074             }
0075 
0076             QVector<qint64> idList;
0077             for (const auto &entity : notification.entities) {
0078                 auto id = getInternalIdentifer(notification.resource, entity);
0079                 if (mEntities.contains(id)) {
0080                     idList << id;
0081                 }
0082             }
0083 
0084             if (idList.isEmpty()) {
0085                 //We don't have this entity in our model
0086                 return;
0087             }
0088             const int newStatus = [&] {
0089                 if (notification.type == Notification::Warning || notification.type == Notification::Error) {
0090                     return ApplicationDomain::SyncStatus::SyncError;
0091                 }
0092                 if (notification.type == Notification::Info) {
0093                     switch (notification.code) {
0094                         case ApplicationDomain::SyncInProgress:
0095                             return ApplicationDomain::SyncInProgress;
0096                         case ApplicationDomain::SyncSuccess:
0097                             return ApplicationDomain::SyncSuccess;
0098                         case ApplicationDomain::SyncError:
0099                             return ApplicationDomain::SyncError;
0100                         case ApplicationDomain::NoSyncStatus:
0101                             break;
0102                     }
0103                     return ApplicationDomain::NoSyncStatus;
0104                 }
0105                 if (notification.type == Notification::Progress) {
0106                     return ApplicationDomain::SyncStatus::SyncInProgress;
0107                 }
0108                 return ApplicationDomain::NoSyncStatus;
0109             }();
0110 
0111             for (const auto id : idList) {
0112                 const auto oldStatus = mEntityStatus.value(id);
0113                 QVector<int> changedRoles;
0114                 if (oldStatus != newStatus) {
0115                     SinkTraceCtx(mLogCtx) << "Status changed for entity:" << newStatus << ", id: " << id;
0116                     mEntityStatus.insert(id, newStatus);
0117                     changedRoles << StatusRole;
0118                 }
0119 
0120                 if (notification.type == Notification::Progress) {
0121                     changedRoles << ProgressRole;
0122                 } else if (notification.type == Notification::Warning || notification.type == Notification::Error) {
0123                     changedRoles << WarningRole;
0124                 }
0125 
0126                 if (!changedRoles.isEmpty()) {
0127                     const auto idx = createIndexFromId(id);
0128                     SinkTraceCtx(mLogCtx) << "Index changed:" << idx << changedRoles;
0129                     //We don't emit the changedRoles because the consuming model likely remaps the role anyways and would then need to translate dataChanged signals as well.
0130                     emit dataChanged(idx, idx);
0131                 }
0132             }
0133         });
0134     }
0135 }
0136 
0137 template <class T, class Ptr>
0138 ModelResult<T, Ptr>::~ModelResult()
0139 {
0140     if (mEmitter) {
0141         mEmitter->waitForMethodExecutionEnd();
0142     }
0143 }
0144 
0145 template <class T, class Ptr>
0146 qint64 ModelResult<T, Ptr>::parentId(const Ptr &value)
0147 {
0148     if (!mQuery.parentProperty().isEmpty()) {
0149         const auto identifier = value->getProperty(mQuery.parentProperty()).toByteArray();
0150         if (!identifier.isEmpty()) {
0151             return getInternalIdentifer(value->resourceInstanceIdentifier(), identifier);
0152         }
0153     }
0154     return 0;
0155 }
0156 
0157 template <class T, class Ptr>
0158 int ModelResult<T, Ptr>::rowCount(const QModelIndex &parent) const
0159 {
0160     Q_ASSERT(QThread::currentThread() == this->thread());
0161     return mTree[getIdentifier(parent)].size();
0162 }
0163 
0164 template <class T, class Ptr>
0165 int ModelResult<T, Ptr>::columnCount(const QModelIndex &parent) const
0166 {
0167     Q_ASSERT(QThread::currentThread() == this->thread());
0168     return mPropertyColumns.size();
0169 }
0170 
0171 template <class T, class Ptr>
0172 QVariant ModelResult<T, Ptr>::headerData(int section, Qt::Orientation orientation, int role) const
0173 {
0174     if (role == Qt::DisplayRole) {
0175         if (section < mPropertyColumns.size()) {
0176             return mPropertyColumns.at(section);
0177         }
0178     }
0179     return QVariant();
0180 }
0181 
0182 template <class T, class Ptr>
0183 QVariant ModelResult<T, Ptr>::data(const QModelIndex &index, int role) const
0184 {
0185     Q_ASSERT(QThread::currentThread() == this->thread());
0186     if (role == DomainObjectRole && index.isValid()) {
0187         Q_ASSERT(mEntities.contains(index.internalId()));
0188         return QVariant::fromValue(mEntities.value(index.internalId()));
0189     }
0190     if (role == DomainObjectBaseRole && index.isValid()) {
0191         Q_ASSERT(mEntities.contains(index.internalId()));
0192         return QVariant::fromValue(mEntities.value(index.internalId()).template staticCast<Sink::ApplicationDomain::ApplicationDomainType>());
0193     }
0194     if (role == ChildrenFetchedRole) {
0195         return childrenFetched(index);
0196     }
0197     if (role == StatusRole) {
0198         auto it = mEntityStatus.constFind(index.internalId());
0199         if (it != mEntityStatus.constEnd()) {
0200             return *it;
0201         }
0202         return {};
0203     }
0204     if (role == Qt::DisplayRole && index.isValid()) {
0205         if (index.column() < mPropertyColumns.size()) {
0206             Q_ASSERT(mEntities.contains(index.internalId()));
0207             auto entity = mEntities.value(index.internalId());
0208             return entity->getProperty(mPropertyColumns.at(index.column())).toString();
0209         } else {
0210             return "No data available";
0211         }
0212     }
0213     return QVariant();
0214 }
0215 
0216 template <class T, class Ptr>
0217 QModelIndex ModelResult<T, Ptr>::index(int row, int column, const QModelIndex &parent) const
0218 {
0219     Q_ASSERT(QThread::currentThread() == this->thread());
0220     const auto id = getIdentifier(parent);
0221     const auto list = mTree.value(id);
0222     if (list.size() > row) {
0223         const auto childId = list.at(row);
0224         return createIndex(row, column, childId);
0225     }
0226     SinkWarningCtx(mLogCtx) << "Index not available " << row << column << parent;
0227     return QModelIndex();
0228 }
0229 
0230 template <class T, class Ptr>
0231 QModelIndex ModelResult<T, Ptr>::createIndexFromId(const qint64 &id) const
0232 {
0233     Q_ASSERT(QThread::currentThread() == this->thread());
0234     if (id == 0) {
0235         return QModelIndex();
0236     }
0237     auto grandParentId = mParents.value(id, 0);
0238     auto row = mTree.value(grandParentId).indexOf(id);
0239     Q_ASSERT(row >= 0);
0240     return createIndex(row, 0, id);
0241 }
0242 
0243 template <class T, class Ptr>
0244 QModelIndex ModelResult<T, Ptr>::parent(const QModelIndex &index) const
0245 {
0246     auto id = getIdentifier(index);
0247     auto parentId = mParents.value(id);
0248     return createIndexFromId(parentId);
0249 }
0250 
0251 template <class T, class Ptr>
0252 bool ModelResult<T, Ptr>::hasChildren(const QModelIndex &parent) const
0253 {
0254     if (mQuery.parentProperty().isEmpty() && parent.isValid()) {
0255         return false;
0256     }
0257     return QAbstractItemModel::hasChildren(parent);
0258 }
0259 
0260 template <class T, class Ptr>
0261 bool ModelResult<T, Ptr>::canFetchMore(const QModelIndex &parent) const
0262 {
0263     //We fetch trees immediately so can never fetch more
0264     if (parent.isValid() || mFetchedAll) {
0265         return false;
0266     }
0267     return true;
0268 }
0269 
0270 template <class T, class Ptr>
0271 void ModelResult<T, Ptr>::fetchMore(const QModelIndex &parent)
0272 {
0273     SinkTraceCtx(mLogCtx) << "Fetching more: " << parent;
0274     Q_ASSERT(QThread::currentThread() == this->thread());
0275     //We only suppor fetchMore for flat lists
0276     if (parent.isValid()) {
0277         return;
0278     }
0279     //There is already a fetch in progress, don't fetch again.
0280     if (mFetchInProgress) {
0281         SinkTraceCtx(mLogCtx) << "A fetch is already in progress.";
0282         return;
0283     }
0284     mFetchInProgress = true;
0285     mFetchComplete = false;
0286     SinkTraceCtx(mLogCtx) << "Fetching more.";
0287     if (loadEntities) {
0288         loadEntities();
0289     } else {
0290         SinkWarningCtx(mLogCtx) << "No way to fetch entities";
0291     }
0292 }
0293 
0294 template <class T, class Ptr>
0295 bool ModelResult<T, Ptr>::allParentsAvailable(qint64 id) const
0296 {
0297     auto p = id;
0298     while (p) {
0299         if (!mEntities.contains(p)) {
0300             return false;
0301         }
0302         p = mParents.value(p, 0);
0303     }
0304     return true;
0305 }
0306 
0307 template <class T, class Ptr>
0308 void ModelResult<T, Ptr>::add(const Ptr &value)
0309 {
0310     const auto childId = qHash(*value);
0311     const auto pId = parentId(value);
0312     if (mEntities.contains(childId)) {
0313 
0314         mEntitiesToRemove.remove(childId);
0315         return;
0316     }
0317     const auto keys = mTree[pId];
0318     int idx = 0;
0319     for (; idx < keys.size(); idx++) {
0320         if (childId < keys.at(idx)) {
0321             break;
0322         }
0323     }
0324     bool parentIsVisible = allParentsAvailable(pId);
0325     // SinkTraceCtx(mLogCtx) << "Inserting rows " << index << parent;
0326     if (parentIsVisible) {
0327         auto parent = createIndexFromId(pId);
0328         beginInsertRows(parent, idx, idx);
0329     }
0330     mEntities.insert(childId, value);
0331     mTree[pId].insert(idx, childId);
0332     mParents.insert(childId, pId);
0333     if (parentIsVisible) {
0334         endInsertRows();
0335     }
0336     // SinkTraceCtx(mLogCtx) << "Inserted rows " << mTree[id].size();
0337 }
0338 
0339 
0340 template <class T, class Ptr>
0341 void ModelResult<T, Ptr>::remove(const Ptr &value)
0342 {
0343     auto childId = qHash(*value);
0344     if (!mEntities.contains(childId)) {
0345         return;
0346     }
0347     //The removed entity will have no properties, but we at least need the parent property.
0348     auto actualEntity = mEntities.value(childId);
0349     auto id = parentId(actualEntity);
0350     auto parent = createIndexFromId(id);
0351     SinkTraceCtx(mLogCtx) << "Removed entity" << childId;
0352     auto index = mTree[id].indexOf(childId);
0353     if (index >= 0) {
0354         beginRemoveRows(parent, index, index);
0355         mEntities.remove(childId);
0356         mTree[id].removeAll(childId);
0357         mParents.remove(childId);
0358         // TODO remove children
0359         endRemoveRows();
0360     }
0361 }
0362 
0363 template <class T, class Ptr>
0364 void ModelResult<T, Ptr>::setFetcher(const std::function<void()> &fetcher)
0365 {
0366     SinkTraceCtx(mLogCtx) << "Setting fetcher";
0367     loadEntities = fetcher;
0368 }
0369 
0370 template <class T, class Ptr>
0371 void ModelResult<T, Ptr>::updateQuery(const Sink::Query &query)
0372 {
0373     SinkTraceCtx(mLogCtx) << "Triggering query update";
0374     mQuery = query;
0375     mPropertyColumns = query.requestedProperties;
0376     mEntitiesToRemove = mEntities.keys().toSet();
0377     mFetchComplete = false;
0378     mFetchInProgress = false;
0379     fetchMore({});
0380 }
0381 
0382 template <class T, class Ptr>
0383 void ModelResult<T, Ptr>::setEmitter(const typename Sink::ResultEmitter<Ptr>::Ptr &emitter)
0384 {
0385     if (mEmitter) {
0386         mEmitter->waitForMethodExecutionEnd();
0387         mEmitter.clear();
0388     }
0389 
0390     setFetcher([this]() { mEmitter->fetch(); });
0391 
0392     QPointer<QObject> guard(this);
0393     emitter->onAdded([this, guard](const Ptr &value) {
0394         SinkTraceCtx(mLogCtx) << "Received addition: " << value->identifier();
0395         Q_ASSERT(guard);
0396         threadBoundary.callInMainThread([this, value, guard]() {
0397             Q_ASSERT(guard);
0398             add(value);
0399         });
0400     });
0401     emitter->onModified([this, guard](const Ptr &value) {
0402         SinkTraceCtx(mLogCtx) << "Received modification: " << value->identifier();
0403         Q_ASSERT(guard);
0404         threadBoundary.callInMainThread([this, value]() {
0405             modify(value);
0406         });
0407     });
0408     emitter->onRemoved([this, guard](const Ptr &value) {
0409         SinkTraceCtx(mLogCtx) << "Received removal: " << value->identifier();
0410         Q_ASSERT(guard);
0411         threadBoundary.callInMainThread([this, value]() {
0412             remove(value);
0413         });
0414     });
0415     emitter->onInitialResultSetComplete([this, guard](bool fetchedAll) {
0416         SinkTraceCtx(mLogCtx) << "Initial result set complete. Fetched all: " << fetchedAll;
0417         Q_ASSERT(guard);
0418         Q_ASSERT(QThread::currentThread() == this->thread());
0419         mFetchInProgress = false;
0420         mFetchedAll = fetchedAll;
0421         mFetchComplete = true;
0422 
0423         for (const auto &entityId : mEntitiesToRemove) {
0424             remove(mEntities.value(entityId));
0425         }
0426         mEntitiesToRemove.clear();
0427 
0428         emit dataChanged({}, {}, QVector<int>() << ChildrenFetchedRole);
0429     });
0430     mEmitter = emitter;
0431 }
0432 
0433 template <class T, class Ptr>
0434 bool ModelResult<T, Ptr>::childrenFetched(const QModelIndex &index) const
0435 {
0436     return mFetchComplete;
0437 }
0438 
0439 template <class T, class Ptr>
0440 void ModelResult<T, Ptr>::modify(const Ptr &value)
0441 {
0442     auto childId = qHash(*value);
0443     if (!mEntities.contains(childId)) {
0444         //Happens because the DatabaseQuery emits modifiations also if the item used to be filtered.
0445         SinkTraceCtx(mLogCtx) << "Tried to modify a value that is not yet part of the model";
0446         add(value);
0447         return;
0448     }
0449     auto id = parentId(value);
0450     auto parent = createIndexFromId(id);
0451     SinkTraceCtx(mLogCtx) << "Modified entity:" << value->identifier() << ", id: " << childId;
0452     auto i = mTree[id].indexOf(childId);
0453     Q_ASSERT(i >= 0);
0454     mEntities.remove(childId);
0455     mEntities.insert(childId, value);
0456     // TODO check for change of parents
0457     auto idx = index(i, 0, parent);
0458     emit dataChanged(idx, idx);
0459 }
0460 
0461 #define REGISTER_TYPE(T) \
0462     template class ModelResult<T, T::Ptr>; \
0463 
0464 SINK_REGISTER_TYPES()