diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ include (GenerateExportHeader) include (ECMGenerateHeaders) include (ECMAddQch) +include (ECMQtDeclareLoggingCategory) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") @@ -129,3 +130,6 @@ # Write out the features feature_summary (WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) +install(FILES kactivities-stats.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) +install(FILES kactivities-stats.categories DESTINATION ${KDE_INSTALL_CONFDIR}) + diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -18,6 +18,8 @@ ResultSetQuickCheckTest.cpp ResultWatcherTest.cpp + ${CMAKE_BINARY_DIR}/src/kactivities-stat-logsettings.cpp + ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/utils/qsqlquery_iterator.cpp ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/common/database/Database.cpp ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/common/database/schema/ResourcesDatabaseSchema.cpp diff --git a/kactivities-stats.categories b/kactivities-stats.categories new file mode 100644 --- /dev/null +++ b/kactivities-stats.categories @@ -0,0 +1 @@ +org.kde.kactivities.stats KActivitiesStats Stats DEFAULT_SEVERITY [WARNING] IDENTIFIER [KACTIVITY_STAT_LOG] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,12 @@ ${KASTATS_CURRENT_ROOT_SOURCE_DIR}/src/utils/qsqlquery_iterator.cpp ) +ecm_qt_declare_logging_category(KActivitiesStats_LIB_SRCS + HEADER kactivities-stat-logsettings.h + IDENTIFIER KACTIVITY_STAT_LOG +CATEGORY_NAME kf5.kactivity.stat) + + qt5_add_dbus_interface ( KActivitiesStats_LIB_SRCS @@ -30,7 +36,10 @@ resourceslinking_interface ) - +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS + KIO + I18n +) add_library ( KF5ActivitiesStats SHARED @@ -147,5 +156,3 @@ FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR} ) - - diff --git a/src/common/database/Database.cpp b/src/common/database/Database.cpp --- a/src/common/database/Database.cpp +++ b/src/common/database/Database.cpp @@ -35,6 +35,8 @@ #include #include +#include "kactivities-stat-logsettings.h" + namespace Common { namespace { @@ -92,7 +94,7 @@ m_open = m_database.open(); if (!m_open) { - qWarning() << "KActivities: Database is not open: " + qCWarning(KACTIVITY_STAT_LOG) << "KActivities: Database is not open: " << m_database.connectionName() << m_database.databaseName() << m_database.lastError(); @@ -105,7 +107,7 @@ ~QSqlDatabaseWrapper() { - qDebug() << "Closing SQL connection: " << m_connectionName; + qCDebug(KACTIVITY_STAT_LOG) << "Closing SQL connection: " << m_connectionName; } QSqlDatabase &get() @@ -202,18 +204,18 @@ auto walResult = ptr->pragma(QStringLiteral("journal_mode = WAL")); if (walResult != QLatin1String("wal")) { - qWarning("KActivities: Database can not be opened in WAL mode. Check the " + qCWarning(KACTIVITY_STAT_LOG) << "KActivities: Database can not be opened in WAL mode. Check the " "SQLite version (required >3.7.0). And whether your filesystem " - "supports shared memory"); + "supports shared memory"; return nullptr; } // We don't have a big database, lets flush the WAL when // it reaches 400k, not 4M as is default ptr->setPragma(QStringLiteral("wal_autocheckpoint = 100")); - qDebug() << "KActivities: Database connection: " << ptr->d->database->connectionName() + qCDebug(KACTIVITY_STAT_LOG) << "KActivities: Database connection: " << ptr->d->database->connectionName() << "\n query_only: " << ptr->pragma(QStringLiteral("query_only")) << "\n journal_mode: " << ptr->pragma(QStringLiteral("journal_mode")) << "\n wal_autocheckpoint: " << ptr->pragma(QStringLiteral("wal_autocheckpoint")) @@ -255,7 +257,7 @@ lastExecutedQuery = query; if (!ignoreErrors && result.lastError().isValid()) { - qWarning() << "SQL: " + qCWarning(KACTIVITY_STAT_LOG) << "SQL: " << "\n error: " << result.lastError() << "\n query: " << query; } diff --git a/src/ioslave/CMakeLists.txt b/src/ioslave/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/src/ioslave/CMakeLists.txt @@ -0,0 +1,12 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kio5_recentdocumentsactivities\") + +add_library(recentdocumentsactivities MODULE recentdocumentsactivities.cpp) +target_link_libraries(recentdocumentsactivities + KF5::KIOCore + KF5::I18n + KF5::Activities + KF5::ActivitiesStats) +set_target_properties(recentdocumentsactivities PROPERTIES OUTPUT_NAME "recentdocumentsactivities") +install(TARGETS recentdocumentsactivities DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio) + +install( FILES recentdocumentsactivities.protocol DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) diff --git a/src/ioslave/recentdocumentsactivities.h b/src/ioslave/recentdocumentsactivities.h new file mode 100644 --- /dev/null +++ b/src/ioslave/recentdocumentsactivities.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 Méven Car + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + * If not, see . + */ + +#ifndef RECENTDOCUMENTACTIVITIES_H +#define RECENTDOCUMENTACTIVITIES_H + +#include +#include + +class RecentDocumentActivities : public KIO::ForwardingSlaveBase +{ +public: + RecentDocumentActivities(const QByteArray &pool, const QByteArray &app); + ~RecentDocumentActivities() override; + +protected: + bool rewriteUrl(const QUrl &url, QUrl &newUrl) override; + void listDir(const QUrl &url) override; + //void prepareUDSEntry(KIO::UDSEntry &entry, bool listing = false) const override; + void stat(const QUrl& url) override; + void mimetype(const QUrl& url) override; +}; + +#endif diff --git a/src/ioslave/recentdocumentsactivities.cpp b/src/ioslave/recentdocumentsactivities.cpp new file mode 100644 --- /dev/null +++ b/src/ioslave/recentdocumentsactivities.cpp @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2019 Méven Car + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + * If not, see . + */ + +#include "recentdocumentsactivities.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#include + +#include +#include +#include + +namespace KAStats = KActivities::Stats; + +using namespace KAStats; +using namespace KAStats::Terms; + +extern "C" int Q_DECL_EXPORT kdemain(int argc, char **argv) +{ + // necessary to use other kio slaves + QCoreApplication app(argc, argv); + app.setApplicationName(QStringLiteral("kio_recentdocumentsactivities")); + if (argc != 4) { + fprintf(stderr, "Usage: kio_recentdocumentsactivities protocol domain-socket1 domain-socket2\n"); + exit(-1); + } + // start the slave + RecentDocumentActivities slave(argv[2], argv[3]); + slave.dispatchLoop(); + return 0; +} + +bool isRootUrl(const QUrl &url) +{ + const QString path = url.adjusted(QUrl::StripTrailingSlash).path(); + return (path.isEmpty() || path == QLatin1String("/")); +} + + +RecentDocumentActivities::RecentDocumentActivities(const QByteArray& pool, const QByteArray& app): + ForwardingSlaveBase("recentdocumentsactivities", pool, app) +{} + +RecentDocumentActivities::~RecentDocumentActivities() +{} + + +bool RecentDocumentActivities::rewriteUrl(const QUrl& url, QUrl& newUrl) +{ + Q_UNUSED(url) + Q_UNUSED(newUrl); + return false; +} + +void RecentDocumentActivities::listDir(const QUrl& url) +{ + if (!isRootUrl(url)) { + error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); + return; + } + + auto query = UsedResources + | RecentlyUsedFirst + | Agent::any() + | Url::file() + | Limit(30); + + // Parse url query parameter + auto urlQuery = QUrlQuery(url); + + // handles type aka mimetype + if (urlQuery.hasQueryItem(QStringLiteral("type"))) { + auto typeValue = urlQuery.queryItemValue(QStringLiteral("type")); + if (typeValue.contains(QLatin1Char(','))) { + // multiple mimetypes were passed + query = query | Type(typeValue.split(QLatin1Char(','))); + } else { + query = query | Type(typeValue); + } + } + + if (urlQuery.hasQueryItem(QStringLiteral("limit"))) { + auto limitValue = urlQuery.queryItemValue(QStringLiteral("limit")); + bool parseOk; + auto limitInt = limitValue.toInt(&parseOk); + if (parseOk) { + query = query | Limit(limitInt); + } + } + + auto model = new ResultModel(query); + + KIO::UDSEntryList udslist; + udslist.reserve(model->rowCount()); + + for(int r = 0; r < model->rowCount(); ++r) { + QModelIndex index = model->index(r, 0); + QString resource = model->data(index, ResultModel::ResourceRole).toString(); + + // the query only returns files and folders + QUrl resourceUrl = QUrl::fromLocalFile(resource); + + KIO::UDSEntry uds; + KIO::StatJob* job = KIO::stat(resourceUrl, KIO::HideProgressInfo); + + // we do not want to wait for the event loop to delete the job + QScopedPointer sp(job); + job->setAutoDelete(false); + if (job->exec()) { + uds = job->statResult(); + } + uds.fastInsert(KIO::UDSEntry::UDS_URL, resourceUrl.toString()); + + udslist << uds; + } + + listEntries(udslist); + finished(); +} + +void RecentDocumentActivities::stat(const QUrl& url) +{ + if (isRootUrl(url)) { + // + // stat the root path + // + + QString dirName = i18n("Recent Documents"); + KIO::UDSEntry uds; + uds.reserve(5); + uds.fastInsert(KIO::UDSEntry::UDS_NAME, dirName); + uds.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, dirName); + uds.fastInsert(KIO::UDSEntry::UDS_DISPLAY_TYPE, dirName); + uds.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QString::fromLatin1("document-open-recent")); + uds.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); + uds.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); + + statEntry(uds); + finished(); + } + else { + error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); + finished(); + } +} + +void RecentDocumentActivities::mimetype(const QUrl& url) +{ + // the root url is always a folder + if (isRootUrl(url)) { + mimeType(QString::fromLatin1("inode/directory")); + finished(); + } + // results are forwarded + else { + ForwardingSlaveBase::mimetype(url); + } +} diff --git a/src/ioslave/recentdocumentsactivities.protocol b/src/ioslave/recentdocumentsactivities.protocol new file mode 100644 --- /dev/null +++ b/src/ioslave/recentdocumentsactivities.protocol @@ -0,0 +1,14 @@ +[Protocol] +X-DocPath=kioslave5/recentdocumentsactivities/index.html +exec=kf5/kio/recentdocumentsactivities +protocol=recentdocumentsactivities +Icon=document-open-recent +input=none +output=filesystem +listing=Name,Type,Size,Date,AccessDate,Access,Owner,Group,Link +reading=true +opening=true +writing=true +deleting=true +maxInstances=1 +Class=:local diff --git a/src/resultmodel.cpp b/src/resultmodel.cpp --- a/src/resultmodel.cpp +++ b/src/resultmodel.cpp @@ -46,13 +46,14 @@ #include "resultwatcher.h" #include "cleaning.h" #include "kactivities/consumer.h" +#include "kactivities-stat-logsettings.h" #include #define MAX_CHUNK_LOAD_SIZE 50 #define MAX_RELOAD_CACHE_SIZE 50 -#define QDBG qDebug() << "KActivitiesStats(" << (void*)this << ")" +#define QDBG qCDebug(KACTIVITY_STAT_LOG) << "KActivitiesStats(" << (void*)this << ")" namespace KActivities { namespace Stats { @@ -109,7 +110,7 @@ int position) { if (!m_orderingConfig.isValid()) { - qWarning() << "We can not reorder the results, no clientId was specified"; + qCWarning(KACTIVITY_STAT_LOG) << "We can not reorder the results, no clientId was specified"; return; } @@ -192,14 +193,14 @@ inline void debug() const { for (const auto& item: m_items) { - qDebug() << "Item: " << item; + qCDebug(KACTIVITY_STAT_LOG) << "Item: " << item; } } void loadOrderingConfig(const QString &activityTag) { if (!m_configFile) { - qDebug() << "Nothing to load - the client id is empty"; + qCDebug(KACTIVITY_STAT_LOG) << "Nothing to load - the client id is empty"; return; } diff --git a/src/resultset.cpp b/src/resultset.cpp --- a/src/resultset.cpp +++ b/src/resultset.cpp @@ -30,6 +30,7 @@ #include #include #include +#include "kactivities-stat-logsettings.h" // Boost and STL #include @@ -134,7 +135,7 @@ : QString())); if (query.lastError().isValid()) { - qWarning() << "[Error at ResultSetPrivate::initQuery]: " << query.lastError(); + qCWarning(KACTIVITY_STAT_LOG) << "[Error at ResultSetPrivate::initQuery]: " << query.lastError(); } } @@ -466,7 +467,7 @@ } result.setLinkedActivities(linkedActivities); - // qDebug() << result.resource() << "linked to activities" << result.linkedActivities(); + // qDebug(KACTIVITY_STAT_LOG) << result.resource() << "linked to activities" << result.linkedActivities(); return result; } @@ -480,7 +481,7 @@ d->database = Database::instance(Database::ResourcesDatabase, Database::ReadOnly); if (!(d->database)) { - qWarning() << "KActivities ERROR: There is no database. This probably means " + qCWarning(KACTIVITY_STAT_LOG) << "KActivities ERROR: There is no database. This probably means " "that you do not have the Activity Manager running, or that " "something else is broken on your system. Recent documents and " "alike will not work!"; diff --git a/src/resultwatcher.cpp b/src/resultwatcher.cpp --- a/src/resultwatcher.cpp +++ b/src/resultwatcher.cpp @@ -32,6 +32,7 @@ // Local #include #include +#include "kactivities-stat-logsettings.h" // Boost and STL #include @@ -53,7 +54,7 @@ #include -#define QDBG qDebug() << "KActivitiesStats(" << (void*)this << ")" +#define QDBG qCDebug(KACTIVITY_STAT_LOG) << "KActivitiesStats(" << (void*)this << ")" namespace KActivities { namespace Stats { @@ -101,7 +102,7 @@ bool activityMatches(const QString &activity) const { #if DEBUG_MATCHERS - qDebug() << "Activity " << activity << "matching against" + qCDebug(KACTIVITY_STAT_LOG) << "Activity " << activity << "matching against" << query.activities(); #endif @@ -122,7 +123,7 @@ bool agentMatches(const QString &agent) const { #if DEBUG_MATCHERS - qDebug() << "Agent " << agent << "matching against" << query.agents(); + qCDebug(KACTIVITY_STAT_LOG) << "Agent " << agent << "matching against" << query.agents(); #endif return kamd::utils::debug_and_return(DEBUG_MATCHERS, " -> returning ", @@ -142,7 +143,7 @@ bool urlMatches(const QString &url) const { #if DEBUG_MATCHERS - qDebug() << "Url " << url << "matching against" << urlFilters; + qCDebug(KACTIVITY_STAT_LOG) << "Url " << url << "matching against" << urlFilters; #endif return kamd::utils::debug_and_return(DEBUG_MATCHERS, " -> returning ", @@ -177,8 +178,8 @@ }); #if DEBUG_MATCHERS - qDebug() << "Type " << "...type..." << "matching against" << query.types(); - qDebug() << "ANY_TYPE_TAG" << ANY_TYPE_TAG; + qCDebug(KACTIVITY_STAT_LOG) << "Type " << "...type..." << "matching against" << query.types(); + qCDebug(KACTIVITY_STAT_LOG) << "ANY_TYPE_TAG" << ANY_TYPE_TAG; #endif return kamd::utils::debug_and_return(DEBUG_MATCHERS, " -> returning ", @@ -206,7 +207,7 @@ const QString &activity) { #if DEBUG_MATCHERS - qDebug() << "Resource has been linked: " << agent << resource << activity; + qCDebug(KACTIVITY_STAT_LOG) << "Resource has been linked: " << agent << resource << activity; #endif // The used resources do not really care about the linked ones @@ -224,7 +225,7 @@ const QString &activity) { #if DEBUG_MATCHERS - qDebug() << "Resource unlinked: " << agent << resource << activity; + qCDebug(KACTIVITY_STAT_LOG) << "Resource unlinked: " << agent << resource << activity; #endif // The used resources do not really care about the linked ones @@ -380,7 +381,7 @@ for (const auto &activity : activities) { for (const auto &agent : agents) { - qDebug() << "Unlink " << agent << resource << activity; + qCDebug(KACTIVITY_STAT_LOG) << "Unlink " << agent << resource << activity; d->linking->UnlinkResourceFromActivity(agent, resource.toString(), activity); }