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 ¬ification) { 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()