Warning, file /frameworks/kactivities-stats/src/resultmodel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic <ivan.cukic(at)kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 // Self 0008 #include "resultmodel.h" 0009 0010 // Qt 0011 #include <QCoreApplication> 0012 #include <QDateTime> 0013 #include <QDebug> 0014 #include <QFile> 0015 #include <QTimer> 0016 0017 // STL 0018 #include <functional> 0019 #include <thread> 0020 0021 // KDE 0022 #include <KSharedConfig> 0023 #include <KConfigGroup> 0024 0025 // Local 0026 #include <common/database/Database.h> 0027 #include <utils/qsqlquery_iterator.h> 0028 #include <utils/slide.h> 0029 #include <utils/member_matcher.h> 0030 #include "resultset.h" 0031 #include "resultwatcher.h" 0032 #include "cleaning.h" 0033 #include "kactivities/consumer.h" 0034 #include "kactivities-stats-logsettings.h" 0035 0036 #include <common/specialvalues.h> 0037 0038 #define MAX_CHUNK_LOAD_SIZE 50 0039 #define MAX_RELOAD_CACHE_SIZE 50 0040 0041 #define QDBG qCDebug(KACTIVITIES_STATS_LOG) << "KActivitiesStats(" << (void*)this << ")" 0042 0043 namespace KActivities { 0044 namespace Stats { 0045 0046 using Common::Database; 0047 0048 class ResultModelPrivate { 0049 public: 0050 ResultModelPrivate(Query query, const QString &clientId, ResultModel *parent) 0051 : cache(this, clientId, query.limit()) 0052 , query(query) 0053 , watcher(query) 0054 , hasMore(true) 0055 , database(Database::instance(Database::ResourcesDatabase, Database::ReadOnly)) 0056 , q(parent) 0057 { 0058 s_privates << this; 0059 } 0060 0061 ~ResultModelPrivate() 0062 { 0063 s_privates.removeAll(this); 0064 } 0065 0066 enum Fetch { 0067 FetchReset, // Remove old data and reload 0068 FetchReload, // Update all data 0069 FetchMore, // Load more data if there is any 0070 }; 0071 0072 class Cache { //_ 0073 public: 0074 typedef QList<ResultSet::Result> Items; 0075 0076 Cache(ResultModelPrivate *d, const QString &clientId, int limit) 0077 : d(d) 0078 , m_countLimit(limit) 0079 , m_clientId(clientId) 0080 { 0081 if (!m_clientId.isEmpty()) { 0082 m_configFile = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-statsrc")); 0083 } 0084 } 0085 0086 ~Cache() 0087 { 0088 } 0089 0090 inline int size() const 0091 { 0092 return m_items.size(); 0093 } 0094 0095 inline void setLinkedResultPosition(const QString &resourcePath, 0096 int position) 0097 { 0098 if (!m_orderingConfig.isValid()) { 0099 qCWarning(KACTIVITIES_STATS_LOG) << "We can not reorder the results, no clientId was specified"; 0100 return; 0101 } 0102 0103 // Preconditions: 0104 // - cache is ordered properly, first on the user's desired order, 0105 // then on the query specified order 0106 // - the resource that needs to be moved is a linked resource, not 0107 // one that comes from the stats (there are overly many 0108 // corner-cases that need to be covered in order to support 0109 // reordering of the statistics-based resources) 0110 // - the new position for the resource is not outside of the cache 0111 0112 auto resourcePosition = find(resourcePath); 0113 0114 if (resourcePosition) { 0115 if (resourcePosition.index == position) { 0116 return; 0117 } 0118 if (resourcePosition.iterator->linkStatus() == ResultSet::Result::NotLinked) { 0119 return; 0120 } 0121 } 0122 0123 // Lets make a list of linked items - we can only reorder them, 0124 // not others 0125 QStringList linkedItems; 0126 0127 for (const ResultSet::Result &item : std::as_const(m_items)) { 0128 if (item.linkStatus() == ResultSet::Result::NotLinked) { 0129 break; 0130 } 0131 linkedItems << item.resource(); 0132 } 0133 0134 // We have two options: 0135 // - we are planning to add an item to the desired position, 0136 // but the item is not yet in the model 0137 // - we want to move an existing item 0138 if (!resourcePosition 0139 || resourcePosition.iterator->linkStatus() == ResultSet::Result::NotLinked) { 0140 0141 linkedItems.insert(position, resourcePath); 0142 0143 m_fixedOrderedItems = linkedItems; 0144 0145 } else { 0146 // We can not accept the new position to be outside 0147 // of the linked items area 0148 if (position >= linkedItems.size()) { 0149 position = linkedItems.size() - 1; 0150 } 0151 0152 Q_ASSERT(resourcePosition.index == linkedItems.indexOf(resourcePath)); 0153 auto oldPosition = linkedItems.indexOf(resourcePath); 0154 0155 kamd::utils::move_one( 0156 linkedItems.begin() + oldPosition, 0157 linkedItems.begin() + position); 0158 0159 // When we change this, the cache is not valid anymore, 0160 // destinationFor will fail and we can not use it 0161 m_fixedOrderedItems = linkedItems; 0162 0163 // We are prepared to reorder the cache 0164 d->repositionResult(resourcePosition, 0165 d->destinationFor(*resourcePosition)); 0166 } 0167 0168 m_orderingConfig.writeEntry("kactivitiesLinkedItemsOrder", m_fixedOrderedItems); 0169 m_orderingConfig.sync(); 0170 0171 // We need to notify others to reload 0172 for (const auto &other : std::as_const(s_privates)) { 0173 if (other != d && other->cache.m_clientId == m_clientId) { 0174 other->fetch(FetchReset); 0175 } 0176 } 0177 } 0178 0179 inline void debug() const 0180 { 0181 for (const auto& item: m_items) { 0182 qCDebug(KACTIVITIES_STATS_LOG) << "Item: " << item; 0183 } 0184 } 0185 0186 void loadOrderingConfig(const QString &activityTag) 0187 { 0188 if (!m_configFile) { 0189 qCDebug(KACTIVITIES_STATS_LOG) << "Nothing to load - the client id is empty"; 0190 return; 0191 } 0192 0193 m_orderingConfig = 0194 KConfigGroup(m_configFile, 0195 QStringLiteral("ResultModel-OrderingFor-") + m_clientId + activityTag); 0196 0197 if (m_orderingConfig.hasKey("kactivitiesLinkedItemsOrder")) { 0198 // If we have the ordering defined, use it 0199 m_fixedOrderedItems = m_orderingConfig.readEntry("kactivitiesLinkedItemsOrder", 0200 QStringList()); 0201 } else { 0202 // Otherwise, copy the order from the previous activity to this one 0203 m_orderingConfig.writeEntry("kactivitiesLinkedItemsOrder", m_fixedOrderedItems); 0204 m_orderingConfig.sync(); 0205 0206 } 0207 } 0208 0209 private: 0210 ResultModelPrivate *const d; 0211 0212 QList<ResultSet::Result> m_items; 0213 int m_countLimit; 0214 0215 QString m_clientId; 0216 KSharedConfig::Ptr m_configFile; 0217 KConfigGroup m_orderingConfig; 0218 QStringList m_fixedOrderedItems; 0219 0220 friend QDebug operator<< (QDebug out, const Cache &cache) 0221 { 0222 for (const auto& item: cache.m_items) { 0223 out << "Cache item: " << item << "\n"; 0224 } 0225 0226 return out; 0227 } 0228 0229 public: 0230 inline const QStringList &fixedOrderedItems() const 0231 { 0232 return m_fixedOrderedItems; 0233 } 0234 0235 //_ Fancy iterator, find, lowerBound 0236 struct FindCacheResult { 0237 Cache *const cache; 0238 Items::iterator iterator; 0239 int index; 0240 0241 FindCacheResult(Cache *cache, Items::iterator iterator) 0242 : cache(cache) 0243 , iterator(iterator) 0244 , index(std::distance(cache->m_items.begin(), iterator)) 0245 { 0246 } 0247 0248 operator bool() const 0249 { 0250 return iterator != cache->m_items.end(); 0251 } 0252 0253 ResultSet::Result &operator*() const 0254 { 0255 return *iterator; 0256 } 0257 0258 ResultSet::Result *operator->() const 0259 { 0260 return &(*iterator); 0261 } 0262 }; 0263 0264 inline FindCacheResult find(const QString &resource) 0265 { 0266 using namespace kamd::utils::member_matcher; 0267 0268 // Non-const iterator because the result is constructed from it 0269 return FindCacheResult( 0270 this, std::find_if(m_items.begin(), m_items.end(), member(&ResultSet::Result::resource) 0271 == resource)); 0272 } 0273 0274 template <typename Predicate> 0275 inline FindCacheResult lowerBoundWithSkippedResource(Predicate &&lessThanPredicate) 0276 { 0277 using namespace kamd::utils::member_matcher; 0278 const int count = std::count_if(m_items.cbegin(), m_items.cend(), 0279 [&] (const ResultSet::Result &result) { 0280 return lessThanPredicate(result, _); 0281 }); 0282 0283 return FindCacheResult(this, m_items.begin() + count); 0284 0285 0286 // using namespace kamd::utils::member_matcher; 0287 // 0288 // const auto position = 0289 // std::lower_bound(m_items.begin(), m_items.end(), 0290 // _, std::forward<Predicate>(lessThanPredicate)); 0291 // 0292 // // We seem to have found the position for the item. 0293 // // The problem is that we might have found the same position 0294 // // we were previously at. Since this function is usually used 0295 // // to reposition the result, we might not be in a completely 0296 // // sorted collection, so the next item(s) could be less than us. 0297 // // We could do this with count_if, but it would be slower 0298 // 0299 // if (position >= m_items.cend() - 1) { 0300 // return FindCacheResult(this, position); 0301 // 0302 // } else if (lessThanPredicate(_, *(position + 1))) { 0303 // return FindCacheResult(this, position); 0304 // 0305 // } else { 0306 // return FindCacheResult( 0307 // this, std::lower_bound(position + 1, m_items.end(), 0308 // _, std::forward<Predicate>(lessThanPredicate))); 0309 // } 0310 } 0311 //^ 0312 0313 inline void insertAt(const FindCacheResult &at, 0314 const ResultSet::Result &result) 0315 { 0316 m_items.insert(at.iterator, result); 0317 } 0318 0319 inline void removeAt(const FindCacheResult &at) 0320 { 0321 m_items.removeAt(at.index); 0322 } 0323 0324 inline const ResultSet::Result &operator[] (int index) const 0325 { 0326 return m_items[index]; 0327 } 0328 0329 inline void clear() 0330 { 0331 if (m_items.size() == 0) { 0332 return; 0333 } 0334 0335 d->q->beginRemoveRows(QModelIndex(), 0, m_items.size() - 1); 0336 m_items.clear(); 0337 d->q->endRemoveRows(); 0338 } 0339 0340 // Algorithm to calculate the edit operations to allow 0341 //_ replaceing items without model reset 0342 inline void replace(const Items &newItems, int from = 0) 0343 { 0344 using namespace kamd::utils::member_matcher; 0345 0346 #if 0 0347 QDBG << "======"; 0348 QDBG << "Old items {"; 0349 for (const auto& item: m_items) { 0350 QDBG << item; 0351 } 0352 QDBG << "}"; 0353 0354 QDBG << "New items to be added at " << from << " {"; 0355 for (const auto& item: newItems) { 0356 QDBG << item; 0357 } 0358 QDBG << "}"; 0359 #endif 0360 0361 0362 // Based on 'The string to string correction problem 0363 // with block moves' paper by Walter F. Tichy 0364 // 0365 // In essence, it goes like this: 0366 // 0367 // Take the first element from the new list, and try to find 0368 // it in the old one. If you can not find it, it is a new item 0369 // item - send the 'inserted' event. 0370 // If you did find it, test whether the following items also 0371 // match. This detects blocks of items that have moved. 0372 // 0373 // In this example, we find 'b', and then detect the rest of the 0374 // moved block 'b' 'c' 'd' 0375 // 0376 // Old items: a[b c d]e f g 0377 // ^ 0378 // / 0379 // New items: [b c d]a f g 0380 // 0381 // After processing one block, just repeat until the end of the 0382 // new list is reached. 0383 // 0384 // Then remove all remaining elements from the old list. 0385 // 0386 // The main addition here compared to the original papers is that 0387 // our 'strings' can not hold two instances of the same element, 0388 // and that we support updating from arbitrary position. 0389 0390 auto newBlockStart = newItems.cbegin(); 0391 0392 // How many items should we add? 0393 // This should remove the need for post-replace-trimming 0394 // in the case where somebody called this with too much new items. 0395 const int maxToReplace = m_countLimit - from; 0396 0397 if (maxToReplace <= 0) { 0398 return; 0399 } 0400 0401 const auto newItemsEnd = 0402 newItems.size() <= maxToReplace ? newItems.cend() : 0403 newItems.cbegin() + maxToReplace; 0404 0405 0406 // Finding the blocks until we reach the end of the newItems list 0407 // 0408 // from = 4 0409 // Old items: X Y Z U a b c d e f g 0410 // ^ oldBlockStart points to the first element 0411 // of the currently processed block in the old list 0412 // 0413 // New items: _ _ _ _ b c d a f g 0414 // ^ newBlockStartIndex is the index of the first 0415 // element of the block that is currently being 0416 // processed (with 'from' offset) 0417 0418 while (newBlockStart != newItemsEnd) { 0419 0420 const int newBlockStartIndex 0421 = from + std::distance(newItems.cbegin(), newBlockStart); 0422 0423 const auto oldBlockStart = std::find_if( 0424 m_items.begin() + from, m_items.end(), 0425 member(&ResultSet::Result::resource) == newBlockStart->resource()); 0426 0427 if (oldBlockStart == m_items.end()) { 0428 // This item was not found in the old cache, so we are 0429 // inserting a new item at the same position it had in 0430 // the newItems array 0431 d->q->beginInsertRows(QModelIndex(), newBlockStartIndex, 0432 newBlockStartIndex); 0433 0434 m_items.insert(newBlockStartIndex, *newBlockStart); 0435 d->q->endInsertRows(); 0436 0437 // This block contained only one item, move on to find 0438 // the next block - it starts from the next item 0439 ++newBlockStart; 0440 0441 } else { 0442 // We are searching for a block of matching items. 0443 // This is a reimplementation of std::mismatch that 0444 // accepts two complete ranges that is available only 0445 // since C++14, so we can not use it. 0446 auto newBlockEnd = newBlockStart; 0447 auto oldBlockEnd = oldBlockStart; 0448 0449 while (newBlockEnd != newItemsEnd && 0450 oldBlockEnd != m_items.end() && 0451 newBlockEnd->resource() == oldBlockEnd->resource()) { 0452 ++newBlockEnd; 0453 ++oldBlockEnd; 0454 } 0455 0456 // We have found matching blocks 0457 // [newBlockStart, newBlockEnd) and [oldBlockStart, newBlockEnd) 0458 const int oldBlockStartIndex 0459 = std::distance(m_items.begin() + from, oldBlockStart); 0460 0461 const int blockSize 0462 = std::distance(oldBlockStart, oldBlockEnd); 0463 0464 if (oldBlockStartIndex != newBlockStartIndex) { 0465 // If these blocks do not have the same start, 0466 // we need to send the move event. 0467 0468 // Note: If there is a crash here, it means we 0469 // are getting a bad query which has duplicate 0470 // results 0471 0472 d->q->beginMoveRows(QModelIndex(), oldBlockStartIndex, 0473 oldBlockStartIndex + blockSize - 1, 0474 QModelIndex(), newBlockStartIndex); 0475 0476 // Moving the items from the old location to the new one 0477 kamd::utils::slide( 0478 oldBlockStart, oldBlockEnd, 0479 m_items.begin() + newBlockStartIndex); 0480 0481 d->q->endMoveRows(); 0482 } 0483 0484 // Skip all the items in this block, and continue with 0485 // the search 0486 newBlockStart = newBlockEnd; 0487 } 0488 } 0489 0490 // We have avoided the need for trimming for the most part, 0491 // but if the newItems list was shorter than needed, we still 0492 // need to trim the rest. 0493 trim(from + newItems.size()); 0494 0495 // Check whether we got an item representing a non-existent file, 0496 // if so, schedule its removal from the database 0497 // we want to do this async so that we don't block 0498 std::thread([=] { 0499 QList<QString> missingResources; 0500 for (const auto &item: newItems) { 0501 // QFile.exists() can be incredibly slow (eg. if resource is on remote filesystem) 0502 if (item.resource().startsWith(QLatin1Char('/')) && !QFile(item.resource()).exists()) { 0503 missingResources << item.resource(); 0504 } 0505 } 0506 0507 if (missingResources.empty()) { 0508 return; 0509 } 0510 0511 QTimer::singleShot(0, this->d->q, [=] { 0512 d->q->forgetResources(missingResources); 0513 }); 0514 }).detach(); 0515 } 0516 //^ 0517 0518 inline void trim() 0519 { 0520 trim(m_countLimit); 0521 } 0522 0523 inline void trim(int limit) 0524 { 0525 if (m_items.size() <= limit) { 0526 return; 0527 } 0528 0529 // Example: 0530 // limit is 5, 0531 // current cache (0, 1, 2, 3, 4, 5, 6, 7), size = 8 0532 // We need to delete from 5 to 7 0533 0534 d->q->beginRemoveRows(QModelIndex(), limit, m_items.size() - 1); 0535 m_items.erase(m_items.begin() + limit, m_items.end()); 0536 d->q->endRemoveRows(); 0537 } 0538 0539 } cache; //^ 0540 0541 struct FixedItemsLessThan { 0542 //_ Compartor that orders the linked items by user-specified order 0543 typedef kamd::utils::member_matcher::placeholder placeholder; 0544 0545 enum Ordering { 0546 PartialOrdering, 0547 FullOrdering, 0548 }; 0549 0550 FixedItemsLessThan(Ordering ordering, 0551 const Cache &cache, 0552 const QString &matchResource = QString()) 0553 : cache(cache), matchResource(matchResource), ordering(ordering) 0554 { 0555 } 0556 0557 bool lessThan(const QString &leftResource, const QString &rightResource) const 0558 { 0559 const auto fixedOrderedItems = cache.fixedOrderedItems(); 0560 0561 const auto indexLeft = fixedOrderedItems.indexOf(leftResource); 0562 const auto indexRight = fixedOrderedItems.indexOf(rightResource); 0563 0564 const bool hasLeft = indexLeft != -1; 0565 const bool hasRight = indexRight != -1; 0566 0567 /* clang-format off */ 0568 return 0569 ( hasLeft && !hasRight) ? true : 0570 (!hasLeft && hasRight) ? false : 0571 ( hasLeft && hasRight) ? indexLeft < indexRight : 0572 (ordering == PartialOrdering ? false : leftResource < rightResource); 0573 /* clang-format on */ 0574 } 0575 0576 template <typename T> 0577 bool operator() (const T &left, placeholder) const 0578 { 0579 return lessThan(left.resource(), matchResource); 0580 } 0581 0582 template <typename T> 0583 bool operator() (placeholder, const T &right) const 0584 { 0585 return lessThan(matchResource, right.resource()); 0586 } 0587 0588 template <typename T, typename V> 0589 bool operator() (const T &left, const V &right) const 0590 { 0591 return lessThan(left.resource(), right.resource()); 0592 } 0593 0594 const Cache &cache; 0595 const QString matchResource; 0596 Ordering ordering; 0597 //^ 0598 }; 0599 0600 inline Cache::FindCacheResult destinationFor(const ResultSet::Result &result) 0601 { 0602 using namespace kamd::utils::member_matcher; 0603 using namespace Terms; 0604 0605 const auto resource = result.resource(); 0606 const auto score = result.score(); 0607 const auto firstUpdate = result.firstUpdate(); 0608 const auto lastUpdate = result.lastUpdate(); 0609 const auto linkStatus = result.linkStatus(); 0610 0611 /* clang-format off */ 0612 #define FIXED_ITEMS_LESS_THAN FixedItemsLessThan(FixedItemsLessThan::PartialOrdering, cache, resource) 0613 #define ORDER_BY(Field) member(&ResultSet::Result::Field) > Field 0614 #define ORDER_BY_FULL(Field) \ 0615 (query.selection() == Terms::AllResources ? \ 0616 cache.lowerBoundWithSkippedResource( \ 0617 FIXED_ITEMS_LESS_THAN \ 0618 && ORDER_BY(linkStatus) \ 0619 && ORDER_BY(Field) \ 0620 && ORDER_BY(resource)) : \ 0621 cache.lowerBoundWithSkippedResource( \ 0622 FIXED_ITEMS_LESS_THAN \ 0623 && ORDER_BY(Field) \ 0624 && ORDER_BY(resource)) \ 0625 ) 0626 0627 const auto destination = 0628 query.ordering() == HighScoredFirst ? ORDER_BY_FULL(score): 0629 query.ordering() == RecentlyUsedFirst ? ORDER_BY_FULL(lastUpdate): 0630 query.ordering() == RecentlyCreatedFirst ? ORDER_BY_FULL(firstUpdate): 0631 /* otherwise */ ORDER_BY_FULL(resource) 0632 ; 0633 #undef ORDER_BY 0634 #undef ORDER_BY_FULL 0635 #undef FIXED_ITEMS_LESS_THAN 0636 0637 /* clang-format on */ 0638 0639 return destination; 0640 } 0641 0642 inline void removeResult(const Cache::FindCacheResult &result) 0643 { 0644 q->beginRemoveRows(QModelIndex(), result.index, result.index); 0645 cache.removeAt(result); 0646 q->endRemoveRows(); 0647 0648 if (query.selection() != Terms::LinkedResources) { 0649 fetch(cache.size(), 1); 0650 } 0651 } 0652 0653 inline void repositionResult(const Cache::FindCacheResult &result, 0654 const Cache::FindCacheResult &destination) 0655 { 0656 // We already have the resource in the cache 0657 // So, it is the time for a reshuffle 0658 const int oldPosition = result.index; 0659 int position = destination.index; 0660 0661 Q_EMIT q->dataChanged(q->index(oldPosition), q->index(oldPosition)); 0662 0663 if (oldPosition == position) { 0664 return; 0665 } 0666 0667 if (position > oldPosition) { 0668 position++; 0669 } 0670 0671 bool moving 0672 = q->beginMoveRows(QModelIndex(), oldPosition, oldPosition, 0673 QModelIndex(), position); 0674 0675 kamd::utils::move_one(result.iterator, destination.iterator); 0676 0677 if (moving) { 0678 q->endMoveRows(); 0679 } 0680 } 0681 0682 void reload() 0683 { 0684 fetch(FetchReload); 0685 } 0686 0687 void init() 0688 { 0689 using namespace std::placeholders; 0690 0691 QObject::connect( 0692 &watcher, &ResultWatcher::resultScoreUpdated, 0693 q, std::bind(&ResultModelPrivate::onResultScoreUpdated, this, _1, _2, _3, _4)); 0694 QObject::connect( 0695 &watcher, &ResultWatcher::resultRemoved, 0696 q, std::bind(&ResultModelPrivate::onResultRemoved, this, _1)); 0697 QObject::connect( 0698 &watcher, &ResultWatcher::resultLinked, 0699 q, std::bind(&ResultModelPrivate::onResultLinked, this, _1)); 0700 QObject::connect( 0701 &watcher, &ResultWatcher::resultUnlinked, 0702 q, std::bind(&ResultModelPrivate::onResultUnlinked, this, _1)); 0703 0704 QObject::connect( 0705 &watcher, &ResultWatcher::resourceTitleChanged, 0706 q, std::bind(&ResultModelPrivate::onResourceTitleChanged, this, _1, _2)); 0707 QObject::connect( 0708 &watcher, &ResultWatcher::resourceMimetypeChanged, 0709 q, std::bind(&ResultModelPrivate::onResourceMimetypeChanged, this, _1, _2)); 0710 0711 QObject::connect( 0712 &watcher, &ResultWatcher::resultsInvalidated, 0713 q, std::bind(&ResultModelPrivate::reload, this)); 0714 0715 if (query.activities().contains(CURRENT_ACTIVITY_TAG)) { 0716 QObject::connect( 0717 &activities, &KActivities::Consumer::currentActivityChanged, q, 0718 std::bind(&ResultModelPrivate::onCurrentActivityChanged, this, _1)); 0719 } 0720 0721 fetch(FetchReset); 0722 } 0723 0724 void fetch(int from, int count) 0725 { 0726 using namespace Terms; 0727 0728 if (from + count > query.limit()) { 0729 count = query.limit() - from; 0730 } 0731 0732 if (count <= 0) { 0733 return; 0734 } 0735 0736 // In order to see whether there are more results, we need to pass 0737 // the count increased by one 0738 ResultSet results(query | Offset(from) | Limit(count + 1)); 0739 0740 auto it = results.begin(); 0741 0742 Cache::Items newItems; 0743 0744 while (count --> 0 && it != results.end()) { 0745 newItems << *it; 0746 ++it; 0747 } 0748 0749 hasMore = (it != results.end()); 0750 0751 // We need to sort the new items for the linked resources 0752 // user-defined reordering. This needs only to be a partial sort, 0753 // the main sorting is done by sqlite 0754 if (query.selection() != Terms::UsedResources) { 0755 std::stable_sort( 0756 newItems.begin(), newItems.end(), 0757 FixedItemsLessThan(FixedItemsLessThan::PartialOrdering, cache)); 0758 } 0759 0760 cache.replace(newItems, from); 0761 } 0762 0763 void fetch(Fetch mode) 0764 { 0765 if (mode == FetchReset) { 0766 // Removing the previously cached data 0767 // and loading all from scratch 0768 cache.clear(); 0769 0770 /* clang-format off */ 0771 const QString activityTag = 0772 query.activities().contains(CURRENT_ACTIVITY_TAG) 0773 ? (QStringLiteral("-ForActivity-") + activities.currentActivity()) 0774 : QStringLiteral("-ForAllActivities"); 0775 /* clang-format on */ 0776 0777 cache.loadOrderingConfig(activityTag); 0778 0779 fetch(0, MAX_CHUNK_LOAD_SIZE); 0780 0781 } else if (mode == FetchReload) { 0782 if (cache.size() > MAX_RELOAD_CACHE_SIZE) { 0783 // If the cache is big, we are pretending 0784 // we were asked to reset the model 0785 fetch(FetchReset); 0786 0787 } else { 0788 // We are only updating the currently 0789 // cached items, nothing more 0790 fetch(0, cache.size()); 0791 0792 } 0793 0794 } else { // FetchMore 0795 // Load a new batch of data 0796 fetch(cache.size(), MAX_CHUNK_LOAD_SIZE); 0797 } 0798 } 0799 0800 void onResultScoreUpdated(const QString &resource, double score, 0801 uint lastUpdate, uint firstUpdate) 0802 { 0803 QDBG << "ResultModelPrivate::onResultScoreUpdated " 0804 << "result added:" << resource 0805 << "score:" << score 0806 << "last:" << lastUpdate 0807 << "first:" << firstUpdate; 0808 0809 // This can also be called when the resource score 0810 // has been updated, so we need to check whether 0811 // we already have it in the cache 0812 const auto result = cache.find(resource); 0813 0814 /* clang-format off */ 0815 ResultSet::Result::LinkStatus linkStatus 0816 = result ? result->linkStatus() 0817 : query.selection() != Terms::UsedResources ? ResultSet::Result::Unknown 0818 : query.selection() != Terms::LinkedResources ? ResultSet::Result::Linked 0819 : ResultSet::Result::NotLinked; 0820 /* clang-format on */ 0821 0822 if (result) { 0823 // We are only updating a result we already had, 0824 // lets fill out the data and send the update signal. 0825 // Move it if necessary. 0826 0827 auto &item = *result.iterator; 0828 0829 item.setScore(score); 0830 item.setLinkStatus(linkStatus); 0831 item.setLastUpdate(lastUpdate); 0832 item.setFirstUpdate(firstUpdate); 0833 0834 repositionResult(result, destinationFor(item)); 0835 0836 } else { 0837 // We do not have the resource in the cache, 0838 // lets fill out the data and insert it 0839 // at the desired position 0840 0841 ResultSet::Result result; 0842 result.setResource(resource); 0843 0844 result.setTitle(QStringLiteral(" ")); 0845 result.setMimetype(QStringLiteral(" ")); 0846 fillTitleAndMimetype(result); 0847 0848 result.setScore(score); 0849 result.setLinkStatus(linkStatus); 0850 result.setLastUpdate(lastUpdate); 0851 result.setFirstUpdate(firstUpdate); 0852 0853 const auto destination = destinationFor(result); 0854 0855 q->beginInsertRows(QModelIndex(), destination.index, 0856 destination.index); 0857 0858 cache.insertAt(destination, result); 0859 0860 q->endInsertRows(); 0861 0862 cache.trim(); 0863 } 0864 } 0865 0866 void onResultRemoved(const QString &resource) 0867 { 0868 const auto result = cache.find(resource); 0869 0870 if (!result) { 0871 return; 0872 } 0873 0874 if (query.selection() == Terms::UsedResources 0875 || result->linkStatus() != ResultSet::Result::Linked) { 0876 removeResult(result); 0877 } 0878 } 0879 0880 void onResultLinked(const QString &resource) 0881 { 0882 if (query.selection() != Terms::UsedResources) { 0883 onResultScoreUpdated(resource, 0, 0, 0); 0884 } 0885 } 0886 0887 void onResultUnlinked(const QString &resource) 0888 { 0889 const auto result = cache.find(resource); 0890 0891 if (!result) { 0892 return; 0893 } 0894 0895 if (query.selection() == Terms::LinkedResources) { 0896 removeResult(result); 0897 0898 } else if (query.selection() == Terms::AllResources) { 0899 // When the result is unlinked, it might go away or not 0900 // depending on its previous usage 0901 reload(); 0902 } 0903 } 0904 0905 Query query; 0906 ResultWatcher watcher; 0907 bool hasMore; 0908 0909 KActivities::Consumer activities; 0910 Common::Database::Ptr database; 0911 0912 //_ Title and mimetype functions 0913 void fillTitleAndMimetype(ResultSet::Result &result) 0914 { 0915 if (!database) { 0916 return; 0917 } 0918 0919 /* clang-format off */ 0920 auto query = database->execQuery( 0921 QStringLiteral("SELECT " 0922 "title, mimetype " 0923 "FROM " 0924 "ResourceInfo " 0925 "WHERE " 0926 "targettedResource = '") + result.resource() + QStringLiteral("'") 0927 ); 0928 /* clang-format on */ 0929 0930 // Only one item at most 0931 for (const auto &item: query) { 0932 result.setTitle(item[QStringLiteral("title")].toString()); 0933 result.setMimetype(item[QStringLiteral("mimetype")].toString()); 0934 } 0935 } 0936 0937 void onResourceTitleChanged(const QString &resource, const QString &title) 0938 { 0939 const auto result = cache.find(resource); 0940 0941 if (!result) { 0942 return; 0943 } 0944 0945 result->setTitle(title); 0946 0947 Q_EMIT q->dataChanged(q->index(result.index), q->index(result.index)); 0948 } 0949 0950 void onResourceMimetypeChanged(const QString &resource, const QString &mimetype) 0951 { 0952 // TODO: This can add or remove items from the model 0953 0954 const auto result = cache.find(resource); 0955 0956 if (!result) { 0957 return; 0958 } 0959 0960 result->setMimetype(mimetype); 0961 0962 Q_EMIT q->dataChanged(q->index(result.index), q->index(result.index)); 0963 } 0964 //^ 0965 0966 void onCurrentActivityChanged(const QString &activity) 0967 { 0968 Q_UNUSED(activity); 0969 // If the current activity has changed, and 0970 // the query lists items for the ':current' one, 0971 // reset the model (not a simple refresh this time) 0972 if (query.activities().contains(CURRENT_ACTIVITY_TAG)) { 0973 fetch(FetchReset); 0974 } 0975 } 0976 0977 private: 0978 ResultModel *const q; 0979 static QList<ResultModelPrivate*> s_privates; 0980 0981 }; 0982 0983 QList<ResultModelPrivate*> ResultModelPrivate::s_privates; 0984 0985 ResultModel::ResultModel(Query query, QObject *parent) 0986 : QAbstractListModel(parent) 0987 , d(new ResultModelPrivate(query, QString(), this)) 0988 { 0989 d->init(); 0990 } 0991 0992 ResultModel::ResultModel(Query query, const QString &clientId, QObject *parent) 0993 : QAbstractListModel(parent) 0994 , d(new ResultModelPrivate(query, clientId, this)) 0995 { 0996 d->init(); 0997 } 0998 0999 ResultModel::~ResultModel() 1000 { 1001 delete d; 1002 } 1003 1004 QHash<int, QByteArray> ResultModel::roleNames() const 1005 { 1006 return { 1007 { ResourceRole , "resource" }, 1008 { TitleRole , "title" }, 1009 { ScoreRole , "score" }, 1010 { FirstUpdateRole , "created" }, 1011 { LastUpdateRole , "modified" }, 1012 { LinkStatusRole , "linkStatus" }, 1013 { LinkedActivitiesRole , "linkedActivities" }, 1014 { MimeType , "mimeType" }, 1015 }; 1016 } 1017 1018 QVariant ResultModel::data(const QModelIndex &item, int role) const 1019 { 1020 const auto row = item.row(); 1021 1022 if (row < 0 || row >= d->cache.size()) { 1023 return QVariant(); 1024 } 1025 1026 const auto &result = d->cache[row]; 1027 1028 /* clang-format off */ 1029 return role == Qt::DisplayRole ? QString( 1030 result.title() + QStringLiteral(" ") + 1031 result.resource() + QStringLiteral(" - ") + 1032 QString::number(result.linkStatus()) + QStringLiteral(" - ") + 1033 QString::number(result.score()) 1034 ) 1035 : role == ResourceRole ? result.resource() 1036 : role == TitleRole ? result.title() 1037 : role == ScoreRole ? result.score() 1038 : role == FirstUpdateRole ? result.firstUpdate() 1039 : role == LastUpdateRole ? result.lastUpdate() 1040 : role == LinkStatusRole ? result.linkStatus() 1041 : role == LinkedActivitiesRole ? result.linkedActivities() 1042 : role == MimeType ? result.mimetype() 1043 : QVariant() 1044 ; 1045 /* clang-format on */ 1046 } 1047 1048 QVariant ResultModel::headerData(int section, Qt::Orientation orientation, 1049 int role) const 1050 { 1051 Q_UNUSED(section); 1052 Q_UNUSED(orientation); 1053 Q_UNUSED(role); 1054 return QVariant(); 1055 } 1056 1057 int ResultModel::rowCount(const QModelIndex &parent) const 1058 { 1059 return parent.isValid() ? 0 : d->cache.size(); 1060 } 1061 1062 void ResultModel::fetchMore(const QModelIndex &parent) 1063 { 1064 if (parent.isValid()) { 1065 return; 1066 } 1067 d->fetch(ResultModelPrivate::FetchMore); 1068 } 1069 1070 bool ResultModel::canFetchMore(const QModelIndex &parent) const 1071 { 1072 return parent.isValid() ? false 1073 : d->cache.size() >= d->query.limit() ? false 1074 : d->hasMore; 1075 } 1076 1077 void ResultModel::forgetResources(const QList<QString> &resources) 1078 { 1079 const auto lstActivities = d->query.activities(); 1080 for (const QString &activity : lstActivities) { 1081 const auto lstAgents = d->query.agents(); 1082 for (const QString &agent : lstAgents) { 1083 for (const QString &resource : resources) { 1084 /* clang-format off */ 1085 Stats::forgetResource( 1086 activity, 1087 agent == CURRENT_AGENT_TAG ? 1088 QCoreApplication::applicationName() : agent, 1089 resource); 1090 /* clang-format on */ 1091 } 1092 } 1093 } 1094 } 1095 1096 void ResultModel::forgetResource(const QString &resource) 1097 { 1098 ResultModel::forgetResources({ resource }); 1099 } 1100 1101 void ResultModel::forgetResource(int row) 1102 { 1103 if (row >= d->cache.size()) { 1104 return; 1105 } 1106 const auto lstActivities = d->query.activities(); 1107 for (const QString &activity : lstActivities) { 1108 const auto lstAgents = d->query.agents(); 1109 for (const QString &agent : lstAgents) { 1110 /* clang-format off */ 1111 Stats::forgetResource( 1112 activity, 1113 agent == CURRENT_AGENT_TAG ? 1114 QCoreApplication::applicationName() : agent, 1115 d->cache[row].resource()); 1116 /* clang-format on */ 1117 } 1118 } 1119 } 1120 1121 void ResultModel::forgetAllResources() 1122 { 1123 Stats::forgetResources(d->query); 1124 } 1125 1126 void ResultModel::setResultPosition(const QString &resource, int position) 1127 { 1128 d->cache.setLinkedResultPosition(resource, position); 1129 } 1130 1131 void ResultModel::sortItems(Qt::SortOrder sortOrder) 1132 { 1133 // TODO 1134 Q_UNUSED(sortOrder); 1135 } 1136 1137 void ResultModel::linkToActivity(const QUrl &resource, 1138 const Terms::Activity &activity, 1139 const Terms::Agent &agent) 1140 { 1141 d->watcher.linkToActivity(resource, activity, agent); 1142 } 1143 1144 void ResultModel::unlinkFromActivity(const QUrl &resource, 1145 const Terms::Activity &activity, 1146 const Terms::Agent &agent) 1147 { 1148 d->watcher.unlinkFromActivity(resource, activity, agent); 1149 } 1150 1151 } // namespace Stats 1152 } // namespace KActivities 1153 1154 // #include "resourcemodel.moc"