File indexing completed on 2025-01-26 05:00:55
0001 /* 0002 * SPDX-FileCopyrightText: 2019 Méven Car (meven.car@kdemail.net) 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "RecentlyUsedEventSpy.h" 0008 0009 #include <QFile> 0010 #include <QStandardPaths> 0011 #include <QString> 0012 #include <QUrl> 0013 #include <QXmlStreamReader> 0014 0015 #include <KApplicationTrader> 0016 #include <KDirWatch> 0017 0018 #include "DebugPluginRecentlyUsedEventSpy.h" 0019 0020 K_PLUGIN_CLASS(RecentlyUsedEventSpyPlugin) 0021 0022 RecentlyUsedEventSpyPlugin::RecentlyUsedEventSpyPlugin(QObject *parent) 0023 : Plugin(parent) 0024 , m_resources(nullptr) 0025 , m_dirWatcher(new KDirWatch(this)) 0026 , m_lastUpdate(QDateTime::currentDateTime()) 0027 { 0028 // recently-used xml history file 0029 // usually $HOME/.local/share/recently-used.xbel 0030 QString filename = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/recently-used.xbel"); 0031 0032 m_dirWatcher->addFile(filename); 0033 connect(m_dirWatcher.get(), &KDirWatch::dirty, this, &RecentlyUsedEventSpyPlugin::fileUpdated); 0034 connect(m_dirWatcher.get(), &KDirWatch::created, this, &RecentlyUsedEventSpyPlugin::fileUpdated); 0035 } 0036 0037 struct Application { 0038 QString name; 0039 QDateTime modified; 0040 }; 0041 0042 class Bookmark 0043 { 0044 public: 0045 QUrl href; 0046 QDateTime added; 0047 QDateTime modified; 0048 QDateTime visited; 0049 QString mimetype; 0050 QList<Application> applications; 0051 0052 QString latestApplication() const; 0053 }; 0054 0055 QString Bookmark::latestApplication() const 0056 { 0057 Application current = applications.first(); 0058 for (const Application &app : applications) { 0059 if (app.modified > current.modified) { 0060 current = app; 0061 } 0062 } 0063 return current.name; 0064 } 0065 0066 void RecentlyUsedEventSpyPlugin::fileUpdated(const QString &filename) 0067 { 0068 QFile file(filename); 0069 if (!file.open(QFile::ReadOnly | QFile::Text)) { 0070 qCWarning(KAMD_LOG_PLUGIN_RECENTLYUSED_EVENTSPY) << "Could not read" << filename; 0071 return; 0072 } 0073 0074 // must parse the xbel xml file 0075 QXmlStreamReader reader(&file); 0076 QList<Bookmark> bookmarks; 0077 Bookmark current; 0078 while (!reader.atEnd()) { 0079 const auto token = reader.readNext(); 0080 if (token == QXmlStreamReader::StartElement) { 0081 if (reader.qualifiedName() == QLatin1String("bookmark")) { 0082 current = Bookmark(); 0083 current.href = QUrl(reader.attributes().value("href").toString()); 0084 QString added = reader.attributes().value("added").toString(); 0085 QString modified = reader.attributes().value("modified").toString(); 0086 QString visited = reader.attributes().value("visited").toString(); 0087 current.added = QDateTime::fromString(added, Qt::ISODate); 0088 current.modified = QDateTime::fromString(modified, Qt::ISODate); 0089 current.visited = QDateTime::fromString(visited, Qt::ISODate); 0090 0091 // application for the current bookmark 0092 } else if (reader.qualifiedName() == QLatin1String("bookmark:application")) { 0093 Application app; 0094 0095 QString exec = reader.attributes().value("exec").toString(); 0096 0097 if (exec.startsWith(QLatin1Char('\'')) && exec.endsWith(QLatin1Char('\''))) { 0098 // remove "'" characters wrapping the command 0099 exec = exec.mid(1, exec.size() - 2); 0100 } 0101 0102 // Search for applications which are executable and case-insensitively match the search term 0103 const KService::List services = KApplicationTrader::query([exec](const KService::Ptr &app) { 0104 return app->exec().compare(exec, Qt::CaseInsensitive) == 0; 0105 }); 0106 0107 if (!services.isEmpty()) { 0108 // use the first item matching 0109 const auto &service = services.first(); 0110 app.name = service->desktopEntryName(); 0111 } else { 0112 // when no services are found, sanitize a little the exec 0113 // remove space and any character after 0114 const int spaceIndex = exec.indexOf(" "); 0115 if (spaceIndex != -1) { 0116 exec = exec.mid(0, spaceIndex); 0117 } 0118 app.name = exec; 0119 } 0120 0121 app.modified = QDateTime::fromString(reader.attributes().value("modified").toString(), Qt::ISODate); 0122 0123 current.applications.append(app); 0124 } else if (reader.qualifiedName() == QLatin1String("mime:mime-type")) { 0125 current.mimetype = reader.attributes().value("type").toString(); 0126 } 0127 } else if (token == QXmlStreamReader::EndElement) { 0128 if (reader.qualifiedName() == QLatin1String("bookmark")) { 0129 // keep track of the finished parsed bookmark 0130 bookmarks << current; 0131 } 0132 } 0133 } 0134 0135 if (reader.hasError()) { 0136 qCWarning(KAMD_LOG_PLUGIN_RECENTLYUSED_EVENTSPY) << "could not parse" << file.fileName() << "error was " << reader.errorString(); 0137 return; 0138 } 0139 0140 // then find the files that were accessed since last run 0141 for (const Bookmark &mark : bookmarks) { 0142 if (mark.added > m_lastUpdate || mark.modified > m_lastUpdate || mark.visited > m_lastUpdate) { 0143 addDocument(mark.href, mark.latestApplication(), mark.mimetype); 0144 } 0145 } 0146 0147 m_lastUpdate = QDateTime::currentDateTime(); 0148 } 0149 0150 void RecentlyUsedEventSpyPlugin::addDocument(const QUrl &url, const QString &application, const QString &mimetype) 0151 { 0152 const auto urlString = url.toString(QUrl::PreferLocalFile); 0153 Plugin::invoke<Qt::QueuedConnection>(m_resources, 0154 "RegisterResourceEvent", 0155 Q_ARG(QString, application), // Application 0156 Q_ARG(uint, 0), // Window ID 0157 Q_ARG(QString, urlString), // URI 0158 Q_ARG(uint, 0) // Event Activities::Accessed 0159 ); 0160 0161 Plugin::invoke<Qt::QueuedConnection>(m_resources, 0162 "RegisteredResourceMimetype", 0163 Q_ARG(QString, urlString), // uri 0164 Q_ARG(QString, mimetype) // mimetype 0165 ); 0166 0167 Plugin::invoke<Qt::QueuedConnection>(m_resources, 0168 "RegisterResourceTitle", 0169 Q_ARG(QString, urlString), // uri 0170 Q_ARG(QString, url.fileName()) // title 0171 ); 0172 } 0173 0174 RecentlyUsedEventSpyPlugin::~RecentlyUsedEventSpyPlugin() 0175 { 0176 } 0177 0178 bool RecentlyUsedEventSpyPlugin::init(QHash<QString, QObject *> &modules) 0179 { 0180 Plugin::init(modules); 0181 0182 m_resources = modules["resources"]; 0183 0184 return true; 0185 } 0186 0187 #include "RecentlyUsedEventSpy.moc" 0188 0189 #include "moc_RecentlyUsedEventSpy.cpp"