8#include "resultmodel.h"
11#include <QCoreApplication>
23#include <KConfigGroup>
24#include <KSharedConfig>
28#include "plasma-activities-stats-logsettings.h"
29#include "plasmaactivities/consumer.h"
31#include "resultwatcher.h"
32#include <common/database/Database.h>
33#include <utils/member_matcher.h>
34#include <utils/qsqlquery_iterator.h>
35#include <utils/slide.h>
37#include <common/specialvalues.h>
39constexpr int s_defaultCacheSize = 50;
41#define QDBG qCDebug(PLASMA_ACTIVITIES_STATS_LOG) << "PlasmaActivitiesStats(" << (void *)this << ")"
47using Common::Database;
49class ResultModelPrivate
52 ResultModelPrivate(Query query,
const QString &clientId, ResultModel *parent)
53 : cache(this, clientId, query.limit())
57 , database(Database::instance(Database::ResourcesDatabase, Database::ReadOnly))
65 s_privates.removeAll(
this);
77 typedef QList<ResultSet::Result> Items;
79 Cache(ResultModelPrivate *d,
const QString &clientId,
int limit)
82 , m_clientId(clientId)
84 if (!m_clientId.isEmpty()) {
85 m_configFile = KSharedConfig::openConfig(QStringLiteral(
"kactivitymanagerd-statsrc"));
93 inline int size()
const
95 return m_items.size();
98 inline void setLinkedResultPosition(
const QString &resourcePath,
int position)
100 if (!m_orderingConfig.isValid()) {
101 qCWarning(PLASMA_ACTIVITIES_STATS_LOG) <<
"We can not reorder the results, no clientId was specified";
114 auto resourcePosition = find(resourcePath);
116 if (resourcePosition) {
117 if (resourcePosition.index == position) {
120 if (resourcePosition.iterator->linkStatus() == ResultSet::Result::NotLinked) {
127 QStringList linkedItems;
129 for (
const ResultSet::Result &item : std::as_const(m_items)) {
130 if (item.linkStatus() == ResultSet::Result::NotLinked) {
133 linkedItems << item.resource();
140 if (!resourcePosition || resourcePosition.iterator->linkStatus() == ResultSet::Result::NotLinked) {
141 linkedItems.
insert(position, resourcePath);
143 m_fixedOrderedItems = linkedItems;
148 if (position >= linkedItems.
size()) {
149 position = linkedItems.
size() - 1;
152 Q_ASSERT(resourcePosition.index == linkedItems.
indexOf(resourcePath));
153 auto oldPosition = linkedItems.
indexOf(resourcePath);
155 kamd::utils::move_one(linkedItems.
begin() + oldPosition, linkedItems.
begin() + position);
159 m_fixedOrderedItems = linkedItems;
162 d->repositionResult(resourcePosition, d->destinationFor(*resourcePosition));
165 m_orderingConfig.writeEntry(
"kactivitiesLinkedItemsOrder", m_fixedOrderedItems);
166 m_orderingConfig.sync();
169 for (
const auto &other : std::as_const(s_privates)) {
170 if (other != d && other->cache.m_clientId == m_clientId) {
171 other->fetch(FetchReset);
176 inline void debug()
const
178 for (
const auto &item : m_items) {
179 qCDebug(PLASMA_ACTIVITIES_STATS_LOG) <<
"Item: " << item;
183 void loadOrderingConfig(
const QString &activityTag)
186 qCDebug(PLASMA_ACTIVITIES_STATS_LOG) <<
"Nothing to load - the client id is empty";
190 m_orderingConfig = KConfigGroup(m_configFile, QStringLiteral(
"ResultModel-OrderingFor-") + m_clientId + activityTag);
192 if (m_orderingConfig.hasKey(
"kactivitiesLinkedItemsOrder")) {
194 m_fixedOrderedItems = m_orderingConfig.readEntry(
"kactivitiesLinkedItemsOrder", QStringList());
197 m_orderingConfig.writeEntry(
"kactivitiesLinkedItemsOrder", m_fixedOrderedItems);
198 m_orderingConfig.sync();
203 ResultModelPrivate *
const d;
205 QList<ResultSet::Result> m_items;
209 KSharedConfig::Ptr m_configFile;
210 KConfigGroup m_orderingConfig;
211 QStringList m_fixedOrderedItems;
213 friend QDebug operator<<(QDebug out,
const Cache &cache)
215 for (
const auto &item : cache.m_items) {
216 out <<
"Cache item: " << item <<
"\n";
223 inline const QStringList &fixedOrderedItems()
const
225 return m_fixedOrderedItems;
229 struct FindCacheResult {
231 Items::iterator iterator;
234 FindCacheResult(Cache *cache, Items::iterator iterator)
241 operator bool()
const
243 return iterator != cache->m_items.end();
246 ResultSet::Result &operator*()
const
251 ResultSet::Result *operator->()
const
257 inline FindCacheResult find(
const QString &resource)
259 using namespace kamd::utils::member_matcher;
265 template<
typename Predicate>
266 inline FindCacheResult lowerBoundWithSkippedResource(Predicate &&lessThanPredicate)
268 using namespace kamd::utils::member_matcher;
269 const int count = std::count_if(m_items.cbegin(), m_items.cend(), [&](
const ResultSet::Result &result) {
270 return lessThanPredicate(result, _);
273 return FindCacheResult(
this, m_items.begin() + count);
302 inline void insertAt(
const FindCacheResult &at,
const ResultSet::Result &result)
304 m_items.insert(at.iterator, result);
307 inline void removeAt(
const FindCacheResult &at)
309 m_items.removeAt(at.index);
312 inline const ResultSet::Result &operator[](
int index)
const
314 return m_items[index];
319 if (m_items.size() == 0) {
323 d->q->beginRemoveRows(QModelIndex(), 0, m_items.size() - 1);
325 d->q->endRemoveRows();
330 inline void replace(
const Items &newItems,
int from = 0)
332 using namespace kamd::utils::member_matcher;
336 QDBG <<
"Old items {";
337 for (
const auto &item : m_items) {
342 QDBG <<
"New items to be added at " << from <<
" {";
343 for (
const auto &item : newItems) {
377 auto newBlockStart = newItems.cbegin();
382 const int maxToReplace = m_countLimit - from;
384 if (maxToReplace <= 0) {
388 const auto newItemsEnd = newItems.size() <= maxToReplace ? newItems.cend() : newItems.cbegin() + maxToReplace;
402 while (newBlockStart != newItemsEnd) {
403 const int newBlockStartIndex = from + std::distance(newItems.cbegin(), newBlockStart);
405 const auto oldBlockStart =
408 if (oldBlockStart == m_items.end()) {
412 d->q->beginInsertRows(QModelIndex(), newBlockStartIndex, newBlockStartIndex);
414 m_items.insert(newBlockStartIndex, *newBlockStart);
415 d->q->endInsertRows();
426 auto newBlockEnd = newBlockStart;
427 auto oldBlockEnd = oldBlockStart;
429 while (newBlockEnd != newItemsEnd && oldBlockEnd != m_items.end() && newBlockEnd->resource() == oldBlockEnd->resource()) {
436 const int oldBlockStartIndex = std::distance(m_items.begin() + from, oldBlockStart);
438 const int blockSize = std::distance(oldBlockStart, oldBlockEnd);
440 if (oldBlockStartIndex != newBlockStartIndex) {
448 d->q->beginMoveRows(QModelIndex(), oldBlockStartIndex, oldBlockStartIndex + blockSize - 1, QModelIndex(), newBlockStartIndex);
451 kamd::utils::slide(oldBlockStart, oldBlockEnd, m_items.begin() + newBlockStartIndex);
458 newBlockStart = newBlockEnd;
465 trim(from + newItems.size());
470 QPointer model{d->q};
471 std::thread([model, newItems] {
472 QList<QString> missingResources;
473 for (
const auto &item : newItems) {
475 if (item.resource().startsWith(QLatin1Char(
'/')) && !QFile(item.resource()).exists()) {
476 missingResources << item.resource();
480 if (missingResources.
empty()) {
486 model->forgetResources(missingResources);
498 inline void trim(
int limit)
500 if (m_items.size() <= limit) {
509 d->q->beginRemoveRows(QModelIndex(), limit, m_items.size() - 1);
510 m_items.erase(m_items.begin() + limit, m_items.end());
511 d->q->endRemoveRows();
516 struct FixedItemsLessThan {
518 typedef kamd::utils::member_matcher::placeholder placeholder;
525 FixedItemsLessThan(Ordering ordering,
const Cache &cache,
const QString &matchResource = QString())
527 , matchResource(matchResource)
532 bool lessThan(
const QString &leftResource,
const QString &rightResource)
const
534 const auto fixedOrderedItems = cache.fixedOrderedItems();
536 const auto indexLeft = fixedOrderedItems.indexOf(leftResource);
537 const auto indexRight = fixedOrderedItems.indexOf(rightResource);
539 const bool hasLeft = indexLeft != -1;
540 const bool hasRight = indexRight != -1;
542 return (hasLeft && !hasRight) ? true
543 : (!hasLeft && hasRight) ?
false
544 : (hasLeft && hasRight) ? indexLeft < indexRight
545 : (ordering == PartialOrdering ? false : leftResource < rightResource);
549 bool operator()(
const T &left, placeholder)
const
551 return lessThan(
left.resource(), matchResource);
555 bool operator()(placeholder,
const T &right)
const
557 return lessThan(matchResource,
right.resource());
560 template<
typename T,
typename V>
561 bool operator()(
const T &left,
const V &right)
const
563 return lessThan(
left.resource(),
right.resource());
567 const QString matchResource;
572 inline Cache::FindCacheResult destinationFor(
const ResultSet::Result &result)
574 using namespace kamd::utils::member_matcher;
575 using namespace Terms;
577 const auto resource = result.resource();
578 const auto score = result.score();
579 const auto firstUpdate = result.firstUpdate();
580 const auto lastUpdate = result.lastUpdate();
581 const auto linkStatus = result.linkStatus();
583#define FIXED_ITEMS_LESS_THAN FixedItemsLessThan(FixedItemsLessThan::PartialOrdering, cache, resource)
584#define ORDER_BY(Field) member(&ResultSet::Result::Field) > Field
585#define ORDER_BY_FULL(Field) \
586 (query.selection() == Terms::AllResources \
587 ? cache.lowerBoundWithSkippedResource(FIXED_ITEMS_LESS_THAN && ORDER_BY(linkStatus) && ORDER_BY(Field) && ORDER_BY(resource)) \
588 : cache.lowerBoundWithSkippedResource(FIXED_ITEMS_LESS_THAN && ORDER_BY(Field) && ORDER_BY(resource)))
590 const auto destination = query.ordering() == HighScoredFirst ? ORDER_BY_FULL(score)
591 : query.ordering() == RecentlyUsedFirst ? ORDER_BY_FULL(lastUpdate)
592 : query.ordering() == RecentlyCreatedFirst ? ORDER_BY_FULL(firstUpdate)
594 ORDER_BY_FULL(resource);
597#undef FIXED_ITEMS_LESS_THAN
602 inline void removeResult(
const Cache::FindCacheResult &result)
604 q->beginRemoveRows(QModelIndex(), result.index, result.index);
605 cache.removeAt(result);
608 if (query.selection() != Terms::LinkedResources) {
609 fetch(cache.size(), 1);
613 inline void repositionResult(
const Cache::FindCacheResult &result,
const Cache::FindCacheResult &destination)
617 const int oldPosition = result.index;
618 int position = destination.index;
620 Q_EMIT q->dataChanged(q->index(oldPosition), q->index(oldPosition));
622 if (oldPosition == position) {
626 if (position > oldPosition) {
630 bool moving = q->beginMoveRows(QModelIndex(), oldPosition, oldPosition, QModelIndex(), position);
632 kamd::utils::move_one(result.iterator, destination.iterator);
646 using namespace std::placeholders;
658 if (query.activities().contains(CURRENT_ACTIVITY_TAG)) {
662 std::bind(&ResultModelPrivate::onCurrentActivityChanged,
this, _1));
668 void fetch(
const int from,
int count)
670 using namespace Terms;
672 if (from + count > query.limit()) {
673 count = query.limit() - from;
682 ResultSet results(query | Offset(from) | Limit(count + 1));
684 auto it = results.begin();
686 Cache::Items newItems;
688 while (count-- > 0 && it != results.end()) {
693 hasMore = (it != results.end());
698 if (query.selection() != Terms::UsedResources) {
699 std::stable_sort(newItems.begin(), newItems.end(), FixedItemsLessThan(FixedItemsLessThan::PartialOrdering, cache));
702 cache.replace(newItems, from);
705 void fetch(Fetch mode)
707 if (mode == FetchReset) {
712 const QString activityTag = query.activities().
contains(CURRENT_ACTIVITY_TAG)
713 ? (QStringLiteral(
"-ForActivity-") + activities.currentActivity())
714 : QStringLiteral(
"-ForAllActivities");
716 cache.loadOrderingConfig(activityTag);
719 fetch(0, qMin(s_defaultCacheSize, query.limit()));
721 }
else if (mode == FetchReload) {
722 if (cache.size() > s_defaultCacheSize) {
730 fetch(0, cache.size());
735 fetch(cache.size(), s_defaultCacheSize);
739 void onResultScoreUpdated(
const QString &resource,
double score, uint lastUpdate, uint firstUpdate)
741 QDBG <<
"ResultModelPrivate::onResultScoreUpdated "
742 <<
"result added:" << resource <<
"score:" << score <<
"last:" << lastUpdate <<
"first:" << firstUpdate;
747 const auto result = cache.find(resource);
749 ResultSet::Result::LinkStatus linkStatus = result ? result->linkStatus()
750 : query.selection() != Terms::UsedResources ? ResultSet::Result::Unknown
751 : query.selection() != Terms::LinkedResources ? ResultSet::Result::Linked
752 : ResultSet::Result::NotLinked;
759 auto &item = *result.iterator;
761 item.setScore(score);
762 item.setLinkStatus(linkStatus);
763 item.setLastUpdate(lastUpdate);
764 item.setFirstUpdate(firstUpdate);
766 repositionResult(result, destinationFor(item));
773 ResultSet::Result result;
774 result.setResource(resource);
776 result.setTitle(QStringLiteral(
" "));
777 result.setMimetype(QStringLiteral(
" "));
778 fillTitleAndMimetype(result);
780 result.setScore(score);
781 result.setLinkStatus(linkStatus);
782 result.setLastUpdate(lastUpdate);
783 result.setFirstUpdate(firstUpdate);
785 const auto destination = destinationFor(result);
787 q->beginInsertRows(QModelIndex(), destination.index, destination.index);
789 cache.insertAt(destination, result);
797 void onResultRemoved(
const QString &resource)
799 const auto result = cache.find(resource);
805 if (query.selection() == Terms::UsedResources || result->linkStatus() != ResultSet::Result::Linked) {
806 removeResult(result);
810 void onResultLinked(
const QString &resource)
812 if (query.selection() != Terms::UsedResources) {
813 onResultScoreUpdated(resource, 0, 0, 0);
817 void onResultUnlinked(
const QString &resource)
819 const auto result = cache.find(resource);
825 if (query.selection() == Terms::LinkedResources) {
826 removeResult(result);
828 }
else if (query.selection() == Terms::AllResources) {
836 ResultWatcher watcher;
839 KActivities::Consumer activities;
840 Common::Database::Ptr database;
843 void fillTitleAndMimetype(ResultSet::Result &result)
849 auto query = database->execQuery(QStringLiteral(
"SELECT "
854 "targettedResource = '")
855 + result.resource() + QStringLiteral(
"'"));
858 for (
const auto &item : query) {
859 result.setTitle(item[QStringLiteral(
"title")].
toString());
860 result.setMimetype(item[QStringLiteral(
"mimetype")].
toString());
864 void onResourceTitleChanged(
const QString &resource,
const QString &title)
866 const auto result = cache.find(resource);
872 result->setTitle(title);
874 Q_EMIT q->dataChanged(q->index(result.index), q->index(result.index));
877 void onResourceMimetypeChanged(
const QString &resource,
const QString &mimetype)
881 const auto result = cache.find(resource);
887 result->setMimetype(mimetype);
889 Q_EMIT q->dataChanged(q->index(result.index), q->index(result.index));
893 void onCurrentActivityChanged(
const QString &activity)
899 if (query.activities().contains(CURRENT_ACTIVITY_TAG)) {
905 ResultModel *
const q;
906 static QList<ResultModelPrivate *> s_privates;
909QList<ResultModelPrivate *> ResultModelPrivate::s_privates;
911ResultModel::ResultModel(
Query query, QObject *parent)
912 : QAbstractListModel(parent)
913 , d(new ResultModelPrivate(
query, QString(), this))
918ResultModel::ResultModel(Query query,
const QString &clientId,
QObject *parent)
920 , d(new ResultModelPrivate(
query, clientId, this))
925ResultModel::~ResultModel()
933 {ResourceRole,
"resource"},
934 {TitleRole,
"title"},
935 {ScoreRole,
"score"},
936 {FirstUpdateRole,
"created"},
937 {LastUpdateRole,
"modified"},
938 {LinkStatusRole,
"linkStatus"},
939 {LinkedActivitiesRole,
"linkedActivities"},
940 {MimeType,
"mimeType"},
946 const auto row = item.
row();
948 if (row < 0 || row >= d->cache.size()) {
952 const auto &result = d->cache[row];
954 return role ==
Qt::DisplayRole ?
QString(result.title() + QStringLiteral(
" ") + result.resource() + QStringLiteral(
" - ")
956 : role == ResourceRole ? result.resource()
957 : role == TitleRole ? result.title()
958 : role == ScoreRole ? result.score()
959 : role == FirstUpdateRole ? result.firstUpdate()
960 : role == LastUpdateRole ? result.lastUpdate()
961 : role == LinkStatusRole ? result.linkStatus()
962 : role == LinkedActivitiesRole ? result.linkedActivities()
963 : role == MimeType ? result.
mimetype()
964 : role ==
Agent ? result.agent()
971 Q_UNUSED(orientation);
976int ResultModel::rowCount(
const QModelIndex &parent)
const
978 return parent.
isValid() ? 0 : d->cache.size();
981void ResultModel::fetchMore(
const QModelIndex &parent)
986 d->fetch(ResultModelPrivate::FetchMore);
989bool ResultModel::canFetchMore(
const QModelIndex &parent)
const
991 return parent.
isValid() ? false : d->cache.size() >= d->query.limit() ? false : d->hasMore;
996 const auto lstActivities = d->query.activities();
997 for (
const QString &activity : lstActivities) {
998 const auto lstAgents = d->query.agents();
999 for (
const QString &agent : lstAgents) {
1000 for (
const QString &resource : resources) {
1014 if (row >= d->cache.size()) {
1017 const auto lstActivities = d->query.activities();
1018 for (
const QString &activity : lstActivities) {
1019 const auto lstAgents = d->query.agents();
1020 for (
const QString &agent : lstAgents) {
1028 Stats::forgetResources(d->query);
1033 d->cache.setLinkedResultPosition(resource, position);
1039 Q_UNUSED(sortOrder);
1044 d->watcher.linkToActivity(resource, activity, agent);
1047void ResultModel::unlinkFromActivity(
const QUrl &resource,
const Terms::Activity &activity,
const Terms::Agent &agent)
1049 d->watcher.unlinkFromActivity(resource, activity, agent);
1055#include "moc_resultmodel.cpp"
void currentActivityChanged(const QString &id)
The activities system tracks resources (documents, contacts, etc.) that the user has used.
void sortItems(Qt::SortOrder sortOrder)
Sort the items by title.
void forgetResource(const QString &resource)
Removes the specified resource from the history.
void setResultPosition(const QString &resource, int position)
Moves the resource to the specified position.
void forgetResources(const QList< QString > &resources)
Removes specified list of resources from the history.
void forgetAllResources()
Clears the history of all resources that match the current model query.
QString resource() const
String representation of resource (can represent an url or a path)
void resourceMimetypeChanged(const QString &resource, const QString &mimetype)
Emitted when the mimetype of a resource has been changed.
void resultsInvalidated()
Emitted when the client should forget about all the results it knew about and reload them.
void resultLinked(const QString &resource)
Emitted when a result has been linked to the activity.
void resultUnlinked(const QString &resource)
Emitted when a result has been unlinked from the activity.
void resultScoreUpdated(const QString &resource, double score, uint lastUpdate, uint firstUpdate)
Emitted when a result has been added or updated.
void resourceTitleChanged(const QString &resource, const QString &title)
Emitted when the title of a resource has been changed.
void resultRemoved(const QString &resource)
Emitted when a result has been added or updated.
char * toString(const EngineQuery &query)
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
KIOCORE_EXPORT MimetypeJob * mimetype(const QUrl &url, JobFlags flags=DefaultFlags)
const QList< QKeySequence > & begin()
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
iterator insert(const_iterator before, parameter_type value)
qsizetype size() const const
bool isValid() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString number(double n, char format, int precision)
qsizetype indexOf(const QRegularExpression &re, qsizetype from) const const
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
Term to filter the resources according the activity in which they were accessed.
Term to filter the resources according the agent (application) which accessed it.