File indexing completed on 2025-01-26 05:00:55
0001 /* 0002 * SPDX-FileCopyrightText: 2011, 2012, 2013, 2014 Ivan Cukic <ivan.cukic(at)kde.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 // Self 0008 #include "StatsPlugin.h" 0009 #include <kactivities-features.h> 0010 0011 // Qt 0012 #include <QDBusConnection> 0013 #include <QFileSystemWatcher> 0014 #include <QStringList> 0015 0016 // KDE 0017 #include <kconfig.h> 0018 #include <kfileitem.h> 0019 0020 // Boost 0021 #include <boost/range/algorithm/binary_search.hpp> 0022 #include <utils/range.h> 0023 0024 // Local 0025 #include "../../Event.h" 0026 #include "Database.h" 0027 #include "ResourceLinking.h" 0028 #include "ResourceScoreMaintainer.h" 0029 #include "Utils.h" 0030 #include "common/specialvalues.h" 0031 #include "resourcescoringadaptor.h" 0032 0033 K_PLUGIN_CLASS(StatsPlugin) 0034 0035 StatsPlugin *StatsPlugin::s_instance = nullptr; 0036 0037 StatsPlugin::StatsPlugin(QObject *parent) 0038 : Plugin(parent) 0039 , m_activities(nullptr) 0040 , m_resources(nullptr) 0041 , m_resourceLinking(new ResourceLinking(this)) 0042 { 0043 s_instance = this; 0044 0045 new ResourcesScoringAdaptor(this); 0046 QDBusConnection::sessionBus().registerObject(QStringLiteral("/ActivityManager/Resources/Scoring"), this); 0047 0048 setName(QStringLiteral("org.kde.ActivityManager.Resources.Scoring")); 0049 } 0050 0051 bool StatsPlugin::init(QHash<QString, QObject *> &modules) 0052 { 0053 Plugin::init(modules); 0054 0055 if (!resourcesDatabase()) { 0056 return false; 0057 } 0058 0059 m_activities = modules[QStringLiteral("activities")]; 0060 m_resources = modules[QStringLiteral("resources")]; 0061 0062 m_resourceLinking->init(); 0063 0064 connect(m_resources, SIGNAL(ProcessedResourceEvents(EventList)), this, SLOT(addEvents(EventList))); 0065 connect(m_resources, SIGNAL(RegisteredResourceMimetype(QString, QString)), this, SLOT(saveResourceMimetype(QString, QString))); 0066 connect(m_resources, SIGNAL(RegisteredResourceTitle(QString, QString)), this, SLOT(saveResourceTitle(QString, QString))); 0067 0068 connect(modules[QStringLiteral("config")], SIGNAL(pluginConfigChanged()), this, SLOT(loadConfiguration())); 0069 0070 loadConfiguration(); 0071 0072 return true; 0073 } 0074 0075 void StatsPlugin::loadConfiguration() 0076 { 0077 auto conf = config(); 0078 conf.config()->reparseConfiguration(); 0079 0080 m_blockedByDefault = conf.readEntry("blocked-by-default", false); 0081 m_blockAll = false; 0082 m_whatToRemember = (WhatToRemember)conf.readEntry("what-to-remember", (int)AllApplications); 0083 0084 m_apps.clear(); 0085 0086 if (m_whatToRemember == SpecificApplications) { 0087 auto apps = conf.readEntry(m_blockedByDefault ? "allowed-applications" : "blocked-applications", QStringList()); 0088 0089 m_apps.unite({apps.begin(), apps.end()}); 0090 } 0091 0092 // Delete old events, as per configuration. 0093 // For people who do not restart their computers, we should do this from 0094 // time to time. Doing this twice a day should be more than enough. 0095 deleteOldEvents(); 0096 m_deleteOldEventsTimer.setInterval(12 * 60 * 60 * 1000); 0097 connect(&m_deleteOldEventsTimer, &QTimer::timeout, this, &StatsPlugin::deleteOldEvents); 0098 0099 // Loading URL filters 0100 m_urlFilters.clear(); 0101 0102 auto filters = conf.readEntry("url-filters", 0103 QStringList{"about:*", // Ignore about: stuff 0104 "*/.*", // Ignore hidden files 0105 "/", // Ignore root 0106 "/tmp/*"} // Ignore everything in /tmp 0107 ); 0108 0109 for (const auto &filter : filters) { 0110 m_urlFilters << Common::starPatternToRegex(filter); 0111 } 0112 0113 // Loading the private activities 0114 m_otrActivities = conf.readEntry("off-the-record-activities", QStringList()); 0115 } 0116 0117 void StatsPlugin::deleteOldEvents() 0118 { 0119 DeleteEarlierStats(QString(), config().readEntry("keep-history-for", 0)); 0120 } 0121 0122 void StatsPlugin::openResourceEvent(const QString &usedActivity, 0123 const QString &initiatingAgent, 0124 const QString &targettedResource, 0125 const QDateTime &start, 0126 const QDateTime &end) 0127 { 0128 Q_ASSERT_X(!initiatingAgent.isEmpty(), "StatsPlugin::openResourceEvent", "Agent should not be empty"); 0129 Q_ASSERT_X(!usedActivity.isEmpty(), "StatsPlugin::openResourceEvent", "Activity should not be empty"); 0130 Q_ASSERT_X(!targettedResource.isEmpty(), "StatsPlugin::openResourceEvent", "Resource should not be empty"); 0131 0132 detectResourceInfo(targettedResource); 0133 0134 Utils::prepare(*resourcesDatabase(), 0135 openResourceEventQuery, 0136 QStringLiteral("INSERT INTO ResourceEvent" 0137 " (usedActivity, initiatingAgent, targettedResource, start, end) " 0138 "VALUES (:usedActivity, :initiatingAgent, :targettedResource, :start, :end)")); 0139 0140 Utils::exec(*resourcesDatabase(), 0141 Utils::FailOnError, 0142 *openResourceEventQuery, 0143 ":usedActivity", 0144 usedActivity, 0145 ":initiatingAgent", 0146 initiatingAgent, 0147 ":targettedResource", 0148 targettedResource, 0149 ":start", 0150 start.toSecsSinceEpoch(), 0151 ":end", 0152 (end.isNull()) ? QVariant() : end.toSecsSinceEpoch()); 0153 } 0154 0155 void StatsPlugin::closeResourceEvent(const QString &usedActivity, const QString &initiatingAgent, const QString &targettedResource, const QDateTime &end) 0156 { 0157 Q_ASSERT_X(!initiatingAgent.isEmpty(), "StatsPlugin::closeResourceEvent", "Agent should not be empty"); 0158 Q_ASSERT_X(!usedActivity.isEmpty(), "StatsPlugin::closeResourceEvent", "Activity should not be empty"); 0159 Q_ASSERT_X(!targettedResource.isEmpty(), "StatsPlugin::closeResourceEvent", "Resource should not be empty"); 0160 0161 Utils::prepare(*resourcesDatabase(), 0162 closeResourceEventQuery, 0163 QStringLiteral("UPDATE ResourceEvent " 0164 "SET end = :end " 0165 "WHERE " 0166 ":usedActivity = usedActivity AND " 0167 ":initiatingAgent = initiatingAgent AND " 0168 ":targettedResource = targettedResource AND " 0169 "end IS NULL")); 0170 0171 Utils::exec(*resourcesDatabase(), 0172 Utils::FailOnError, 0173 *closeResourceEventQuery, 0174 ":usedActivity", 0175 usedActivity, 0176 ":initiatingAgent", 0177 initiatingAgent, 0178 ":targettedResource", 0179 targettedResource, 0180 ":end", 0181 end.toSecsSinceEpoch()); 0182 } 0183 0184 void StatsPlugin::detectResourceInfo(const QString &_uri) 0185 { 0186 const QUrl uri = QUrl::fromUserInput(_uri); 0187 0188 if (!uri.isLocalFile()) 0189 return; 0190 0191 const QString file = uri.toLocalFile(); 0192 0193 if (!QFile::exists(file)) 0194 return; 0195 0196 KFileItem item(uri); 0197 0198 if (insertResourceInfo(file)) { 0199 saveResourceMimetype(file, item.mimetype(), true); 0200 0201 const auto text = item.text(); 0202 saveResourceTitle(file, text.isEmpty() ? _uri : text, true); 0203 } 0204 } 0205 0206 bool StatsPlugin::insertResourceInfo(const QString &uri) 0207 { 0208 Utils::prepare(*resourcesDatabase(), 0209 getResourceInfoQuery, 0210 QStringLiteral("SELECT targettedResource FROM ResourceInfo WHERE " 0211 " targettedResource = :targettedResource ")); 0212 0213 getResourceInfoQuery->bindValue(":targettedResource", uri); 0214 Utils::exec(*resourcesDatabase(), Utils::FailOnError, *getResourceInfoQuery); 0215 0216 if (getResourceInfoQuery->next()) { 0217 return false; 0218 } 0219 0220 Utils::prepare(*resourcesDatabase(), 0221 insertResourceInfoQuery, 0222 QStringLiteral("INSERT INTO ResourceInfo( " 0223 " targettedResource" 0224 ", title" 0225 ", autoTitle" 0226 ", mimetype" 0227 ", autoMimetype" 0228 ") VALUES (" 0229 " :targettedResource" 0230 ", '' " 0231 ", 1 " 0232 ", '' " 0233 ", 1 " 0234 ")")); 0235 0236 Utils::exec(*resourcesDatabase(), Utils::FailOnError, *insertResourceInfoQuery, ":targettedResource", uri); 0237 0238 return true; 0239 } 0240 0241 void StatsPlugin::saveResourceTitle(const QString &uri, const QString &title, bool autoTitle) 0242 { 0243 if (m_blockAll || m_whatToRemember == NoApplications) { 0244 return; 0245 } 0246 0247 insertResourceInfo(uri); 0248 0249 DATABASE_TRANSACTION(*resourcesDatabase()); 0250 0251 Utils::prepare(*resourcesDatabase(), 0252 saveResourceTitleQuery, 0253 QStringLiteral("UPDATE ResourceInfo SET " 0254 " title = :title" 0255 ", autoTitle = :autoTitle " 0256 "WHERE " 0257 "targettedResource = :targettedResource ")); 0258 0259 Utils::exec(*resourcesDatabase(), 0260 Utils::FailOnError, 0261 *saveResourceTitleQuery, 0262 ":targettedResource", 0263 uri, 0264 ":title", 0265 title, 0266 ":autoTitle", 0267 (autoTitle ? "1" : "0")); 0268 } 0269 0270 void StatsPlugin::saveResourceMimetype(const QString &uri, const QString &mimetype, bool autoMimetype) 0271 { 0272 if (m_blockAll || m_whatToRemember == NoApplications) { 0273 return; 0274 } 0275 0276 insertResourceInfo(uri); 0277 0278 DATABASE_TRANSACTION(*resourcesDatabase()); 0279 0280 Utils::prepare(*resourcesDatabase(), 0281 saveResourceMimetypeQuery, 0282 QStringLiteral("UPDATE ResourceInfo SET " 0283 " mimetype = :mimetype" 0284 ", autoMimetype = :autoMimetype " 0285 "WHERE " 0286 "targettedResource = :targettedResource ")); 0287 0288 Utils::exec(*resourcesDatabase(), 0289 Utils::FailOnError, 0290 *saveResourceMimetypeQuery, 0291 ":targettedResource", 0292 uri, 0293 ":mimetype", 0294 mimetype, 0295 ":autoMimetype", 0296 (autoMimetype ? "1" : "0")); 0297 } 0298 0299 StatsPlugin *StatsPlugin::self() 0300 { 0301 return s_instance; 0302 } 0303 0304 bool StatsPlugin::acceptedEvent(const Event &event) 0305 { 0306 using std::any_of; 0307 using std::bind; 0308 using namespace std::placeholders; 0309 0310 return !( 0311 // If the URI is empty, we do not want to process it 0312 event.uri.isEmpty() || 0313 0314 // Skip if the current activity is OTR 0315 m_otrActivities.contains(currentActivity()) || 0316 0317 // Exclude URIs that match the ignored patterns 0318 any_of(m_urlFilters.cbegin(), m_urlFilters.cend(), [event] (const QRegularExpression ®ex){ return regex.match(event.uri).hasMatch(); }) || 0319 0320 // if blocked by default, the list contains allowed applications 0321 // ignore event if the list doesn't contain the application 0322 // if not blocked by default, the list contains blocked applications 0323 // ignore event if the list contains the application 0324 (m_whatToRemember == SpecificApplications && m_blockedByDefault != boost::binary_search(m_apps, event.application))); 0325 } 0326 0327 Event StatsPlugin::validateEvent(Event event) 0328 { 0329 if (event.uri.startsWith(QStringLiteral("file://"))) { 0330 event.uri = QUrl(event.uri).toLocalFile(); 0331 } 0332 0333 if (event.uri.startsWith(QStringLiteral("/"))) { 0334 QFileInfo file(event.uri); 0335 0336 event.uri = file.exists() ? file.canonicalFilePath() : QString(); 0337 } 0338 0339 return event; 0340 } 0341 0342 QStringList StatsPlugin::listActivities() const 0343 { 0344 return Plugin::retrieve<QStringList>(m_activities, "ListActivities"); 0345 } 0346 0347 QString StatsPlugin::currentActivity() const 0348 { 0349 return Plugin::retrieve<QString>(m_activities, "CurrentActivity"); 0350 } 0351 0352 void StatsPlugin::addEvents(const EventList &events) 0353 { 0354 using namespace kamd::utils; 0355 0356 if (m_blockAll || m_whatToRemember == NoApplications) { 0357 return; 0358 } 0359 0360 const auto &eventsToProcess = events | transformed(&StatsPlugin::validateEvent, this) | filtered(&StatsPlugin::acceptedEvent, this); 0361 0362 if (eventsToProcess.begin() == eventsToProcess.end()) 0363 return; 0364 0365 DATABASE_TRANSACTION(*resourcesDatabase()); 0366 0367 for (const auto &event : eventsToProcess) { 0368 switch (event.type) { 0369 case Event::Accessed: 0370 openResourceEvent(currentActivity(), event.application, event.uri, event.timestamp, event.timestamp); 0371 ResourceScoreMaintainer::self()->processResource(event.uri, event.application); 0372 0373 break; 0374 0375 case Event::Opened: 0376 openResourceEvent(currentActivity(), event.application, event.uri, event.timestamp); 0377 0378 break; 0379 0380 case Event::Closed: 0381 closeResourceEvent(currentActivity(), event.application, event.uri, event.timestamp); 0382 ResourceScoreMaintainer::self()->processResource(event.uri, event.application); 0383 0384 break; 0385 0386 case Event::UserEventType: 0387 ResourceScoreMaintainer::self()->processResource(event.uri, event.application); 0388 break; 0389 0390 default: 0391 // Nothing yet 0392 // TODO: Add focus and modification 0393 break; 0394 } 0395 } 0396 } 0397 0398 void StatsPlugin::DeleteRecentStats(const QString &activity, int count, const QString &what) 0399 { 0400 const auto usedActivity = activity.isEmpty() ? QVariant() : QVariant(activity); 0401 0402 // If we need to delete everything, 0403 // no need to bother with the count and the date 0404 0405 DATABASE_TRANSACTION(*resourcesDatabase()); 0406 0407 if (what == QStringLiteral("everything")) { 0408 // Instantiating these every time is not a big overhead 0409 // since this method is rarely executed. 0410 0411 auto removeResourceInfoQuery = resourcesDatabase()->createQuery(); 0412 removeResourceInfoQuery.prepare( 0413 "DELETE FROM ResourceInfo " 0414 "WHERE ResourceInfo.targettedResource IN (" 0415 " SELECT ResourceEvent.targettedResource " 0416 " FROM ResourceEvent " 0417 " WHERE usedActivity = COALESCE(:usedActivity, usedActivity)" 0418 ")"); 0419 0420 auto removeEventsQuery = resourcesDatabase()->createQuery(); 0421 removeEventsQuery.prepare( 0422 "DELETE FROM ResourceEvent " 0423 "WHERE usedActivity = COALESCE(:usedActivity, usedActivity)"); 0424 0425 auto removeScoreCachesQuery = resourcesDatabase()->createQuery(); 0426 removeScoreCachesQuery.prepare( 0427 "DELETE FROM ResourceScoreCache " 0428 "WHERE usedActivity = COALESCE(:usedActivity, usedActivity)"); 0429 0430 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeResourceInfoQuery, ":usedActivity", usedActivity); 0431 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeEventsQuery, ":usedActivity", usedActivity); 0432 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeScoreCachesQuery, ":usedActivity", usedActivity); 0433 0434 } else { 0435 // Deleting a specified length of time 0436 0437 auto since = QDateTime::currentDateTime(); 0438 0439 since = (what[0] == QLatin1Char('h')) ? since.addSecs(-count * 60 * 60) 0440 : (what[0] == QLatin1Char('d')) ? since.addDays(-count) 0441 : (what[0] == QLatin1Char('m')) ? since.addMonths(-count) 0442 : since; 0443 0444 // Maybe we should decrease the scores for the previously 0445 // cached items. Thinking it is not that important - 0446 // if something was accessed before, and the user did not 0447 // remove the history, it is not really a secret. 0448 0449 auto removeResourceInfoQuery = resourcesDatabase()->createQuery(); 0450 removeResourceInfoQuery.prepare( 0451 "DELETE FROM ResourceInfo " 0452 "WHERE ResourceInfo.targettedResource IN (" 0453 " SELECT ResourceEvent.targettedResource " 0454 " FROM ResourceEvent " 0455 " WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " 0456 " AND end > :since" 0457 ")"); 0458 0459 auto removeEventsQuery = resourcesDatabase()->createQuery(); 0460 removeEventsQuery.prepare( 0461 "DELETE FROM ResourceEvent " 0462 "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " 0463 "AND end > :since"); 0464 0465 auto removeScoreCachesQuery = resourcesDatabase()->createQuery(); 0466 removeScoreCachesQuery.prepare( 0467 "DELETE FROM ResourceScoreCache " 0468 "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " 0469 "AND firstUpdate > :since"); 0470 0471 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeResourceInfoQuery, ":usedActivity", usedActivity, ":since", since.toSecsSinceEpoch()); 0472 0473 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeEventsQuery, ":usedActivity", usedActivity, ":since", since.toSecsSinceEpoch()); 0474 0475 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeScoreCachesQuery, ":usedActivity", usedActivity, ":since", since.toSecsSinceEpoch()); 0476 } 0477 0478 Q_EMIT RecentStatsDeleted(activity, count, what); 0479 } 0480 0481 void StatsPlugin::DeleteEarlierStats(const QString &activity, int months) 0482 { 0483 if (months == 0) { 0484 return; 0485 } 0486 0487 // Deleting a specified length of time 0488 0489 DATABASE_TRANSACTION(*resourcesDatabase()); 0490 0491 const auto time = QDateTime::currentDateTime().addMonths(-months); 0492 const auto usedActivity = activity.isEmpty() ? QVariant() : QVariant(activity); 0493 0494 auto removeResourceInfoQuery = resourcesDatabase()->createQuery(); 0495 removeResourceInfoQuery.prepare( 0496 "DELETE FROM ResourceInfo " 0497 "WHERE ResourceInfo.targettedResource IN (" 0498 " SELECT ResourceEvent.targettedResource " 0499 " FROM ResourceEvent " 0500 " WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " 0501 " AND start < :time" 0502 ")"); 0503 0504 auto removeEventsQuery = resourcesDatabase()->createQuery(); 0505 removeEventsQuery.prepare( 0506 "DELETE FROM ResourceEvent " 0507 "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " 0508 "AND start < :time"); 0509 0510 auto removeScoreCachesQuery = resourcesDatabase()->createQuery(); 0511 removeScoreCachesQuery.prepare( 0512 "DELETE FROM ResourceScoreCache " 0513 "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " 0514 "AND lastUpdate < :time"); 0515 0516 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeResourceInfoQuery, ":usedActivity", usedActivity, ":time", time.toSecsSinceEpoch()); 0517 0518 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeEventsQuery, ":usedActivity", usedActivity, ":time", time.toSecsSinceEpoch()); 0519 0520 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeScoreCachesQuery, ":usedActivity", usedActivity, ":time", time.toSecsSinceEpoch()); 0521 0522 Q_EMIT EarlierStatsDeleted(activity, months); 0523 } 0524 0525 void StatsPlugin::DeleteStatsForResource(const QString &activity, const QString &client, const QString &resource) 0526 { 0527 Q_ASSERT_X(!client.isEmpty(), "StatsPlugin::DeleteStatsForResource", "Agent should not be empty"); 0528 Q_ASSERT_X(!activity.isEmpty(), "StatsPlugin::DeleteStatsForResource", "Activity should not be empty"); 0529 Q_ASSERT_X(!resource.isEmpty(), "StatsPlugin::DeleteStatsForResource", "Resource should not be empty"); 0530 Q_ASSERT_X(client != CURRENT_AGENT_TAG, "StatsPlugin::DeleteStatsForResource", "We can not handle CURRENT_AGENT_TAG here"); 0531 0532 DATABASE_TRANSACTION(*resourcesDatabase()); 0533 0534 // Check against sql injection 0535 if (activity.contains('\'') || client.contains('\'')) 0536 return; 0537 0538 const auto activityFilter = 0539 activity == ANY_ACTIVITY_TAG ? " 1 " : QStringLiteral(" usedActivity = '%1' ").arg(activity == CURRENT_ACTIVITY_TAG ? currentActivity() : activity); 0540 0541 const auto clientFilter = client == ANY_AGENT_TAG ? " 1 " : QStringLiteral(" initiatingAgent = '%1' ").arg(client); 0542 0543 auto removeResourceInfoQuery = resourcesDatabase()->createQuery(); 0544 removeResourceInfoQuery.prepare( 0545 "DELETE FROM ResourceInfo " 0546 "WHERE targettedResource LIKE :targettedResource ESCAPE '\\'"); 0547 0548 auto removeEventsQuery = resourcesDatabase()->createQuery(); 0549 removeEventsQuery.prepare( 0550 "DELETE FROM ResourceEvent " 0551 "WHERE " 0552 + activityFilter + " AND " + clientFilter + " AND " + "targettedResource LIKE :targettedResource ESCAPE '\\'"); 0553 0554 auto removeScoreCachesQuery = resourcesDatabase()->createQuery(); 0555 removeScoreCachesQuery.prepare( 0556 "DELETE FROM ResourceScoreCache " 0557 "WHERE " 0558 + activityFilter + " AND " + clientFilter + " AND " + "targettedResource LIKE :targettedResource ESCAPE '\\'"); 0559 0560 const auto pattern = Common::starPatternToLike(resource); 0561 0562 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeResourceInfoQuery, ":targettedResource", pattern); 0563 0564 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeEventsQuery, ":targettedResource", pattern); 0565 0566 Utils::exec(*resourcesDatabase(), Utils::FailOnError, removeScoreCachesQuery, ":targettedResource", pattern); 0567 0568 Q_EMIT ResourceScoreDeleted(activity, client, resource); 0569 } 0570 0571 bool StatsPlugin::isFeatureOperational(const QStringList &feature) const 0572 { 0573 if (feature[0] == "isOTR") { 0574 if (feature.size() != 2) 0575 return true; 0576 0577 const auto activity = feature[1]; 0578 0579 return activity == "activity" // 0580 || activity == "current" // 0581 || listActivities().contains(activity); 0582 0583 return true; 0584 } 0585 0586 return false; 0587 } 0588 0589 // bool StatsPlugin::isFeatureEnabled(const QStringList &feature) const 0590 // { 0591 // if (feature[0] == "isOTR") { 0592 // if (feature.size() != 2) return false; 0593 // 0594 // auto activity = feature[1]; 0595 // 0596 // if (activity == "activity" || activity == "current") { 0597 // activity = currentActivity(); 0598 // } 0599 // 0600 // return m_otrActivities.contains(activity); 0601 // } 0602 // 0603 // return false; 0604 // } 0605 // 0606 // void StatsPlugin::setFeatureEnabled(const QStringList &feature, bool value) 0607 // { 0608 // if (feature[0] == "isOTR") { 0609 // if (feature.size() != 2) return; 0610 // 0611 // auto activity = feature[1]; 0612 // 0613 // if (activity == "activity" || activity == "current") { 0614 // activity = currentActivity(); 0615 // } 0616 // 0617 // if (!m_otrActivities.contains(activity)) { 0618 // m_otrActivities << activity; 0619 // config().writeEntry("off-the-record-activities", m_otrActivities); 0620 // config().sync(); 0621 // } 0622 // } 0623 // } 0624 0625 QDBusVariant StatsPlugin::featureValue(const QStringList &feature) const 0626 { 0627 if (feature[0] == "isOTR") { 0628 if (feature.size() != 2) 0629 return QDBusVariant(false); 0630 0631 auto activity = feature[1]; 0632 0633 if (activity == "activity" || activity == "current") { 0634 activity = currentActivity(); 0635 } 0636 0637 return QDBusVariant(m_otrActivities.contains(activity)); 0638 } 0639 0640 return QDBusVariant(false); 0641 } 0642 0643 void StatsPlugin::setFeatureValue(const QStringList &feature, const QDBusVariant &value) 0644 { 0645 if (feature[0] == "isOTR") { 0646 if (feature.size() != 2) 0647 return; 0648 0649 auto activity = feature[1]; 0650 0651 if (activity == "activity" || activity == "current") { 0652 activity = currentActivity(); 0653 } 0654 0655 bool isOTR = value.variant().toBool(); 0656 0657 if (isOTR && !m_otrActivities.contains(activity)) { 0658 m_otrActivities << activity; 0659 0660 } else if (!isOTR && m_otrActivities.contains(activity)) { 0661 m_otrActivities.removeAll(activity); 0662 } 0663 0664 config().writeEntry("off-the-record-activities", m_otrActivities); 0665 config().sync(); 0666 } 0667 } 0668 0669 QStringList StatsPlugin::listFeatures(const QStringList &feature) const 0670 { 0671 if (feature.isEmpty() || feature[0].isEmpty()) { 0672 return {"isOTR/"}; 0673 0674 } else if (feature[0] == "isOTR") { 0675 return listActivities(); 0676 } 0677 0678 return QStringList(); 0679 } 0680 0681 #include "StatsPlugin.moc" 0682 0683 #include "moc_StatsPlugin.cpp"