diff --git a/include/DtkCore/DPlugin b/include/DtkCore/DPlugin new file mode 100644 index 00000000..dab921e6 --- /dev/null +++ b/include/DtkCore/DPlugin @@ -0,0 +1 @@ +#include "dplugin.h" diff --git a/include/util/dplugin.h b/include/util/dplugin.h new file mode 100644 index 00000000..27ff6822 --- /dev/null +++ b/include/util/dplugin.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2017 - 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef DPLUGIN_H +#define DPLUGIN_H + +#include "dtkcore_global.h" + +#include + +#include + +DCORE_BEGIN_NAMESPACE + +class DPluginPrivate; + +class DPlugin : public QObject +{ + Q_OBJECT; + +public: + static QList> listPlugins(const QStringList &extraLocations = {}); + + const QScopedPointer library; + QString name(); + +private: + DPlugin(const QString &name, const QString &filename, const QString &version, QObject *parent = nullptr); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(DPlugin); +}; + +DCORE_END_NAMESPACE + +#endif diff --git a/src/util/dplugin.cpp b/src/util/dplugin.cpp new file mode 100644 index 00000000..d8024938 --- /dev/null +++ b/src/util/dplugin.cpp @@ -0,0 +1,129 @@ +// SPDX-FileCopyrightText: 2019 - 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "dplugin.h" +#include "dplugin_p.h" + +#include +#include +#include +#include +#include + +DCORE_BEGIN_NAMESPACE + +QList> DPlugin::listPlugins(const QStringList &extraDirs) +{ + auto pluginDirs = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); + + for (auto &pluginDir : pluginDirs) { + pluginDir = QStringList{pluginDir, "plugins"}.join(QDir::separator()); + } + + pluginDirs.append(extraDirs); + + qDebug() << "Searching dplugins in" << pluginDirs; + + QList> plugins{}; + + for (const auto &pluginDir : pluginDirs) { + plugins << DPluginPrivate::searchPluginsInPath(pluginDir); + } + + return plugins; +} + +QList> DPluginPrivate::searchPluginsInPath(const QString &pluginDirPath) +{ + auto const pluginDir = QDir(pluginDirPath); + auto const entryList = pluginDir.entryList(); + + QList> result{}; + + for (auto const &entry : entryList) { + auto filePath = QStringList{pluginDirPath, entry}.join(QDir::separator()); + auto fileInfo = QFileInfo(filePath); + + // NOTE(black_desk): we do not load a large file (>4KB) into memory + if (!fileInfo.isFile() || fileInfo.size() > (1 << 11)) { + continue; + } + + QFile file(filePath); + file.open(QIODevice::ReadOnly); + auto content = file.readAll(); + + auto plugin = DPluginPrivate::pluginFromJsonContent(content); + if (!plugin) { + qWarning() << "Ignore invalid plugin config:" << filePath; + continue; + } + result << plugin; + } + + return result; +} + +std::shared_ptr DPluginPrivate::pluginFromJsonContent(const QByteArray &content) +{ + QJsonParseError err; + auto jsonDoc = QJsonDocument::fromJson(content, &err); + if (err.error || !jsonDoc.isObject()) { + return nullptr; + } + + auto config = jsonDoc.object(); + if (!DPluginPrivate::configCompatible(config["pluginConfigVersion"])) { + return nullptr; + } + + auto nameVar = config["pluginName"]; + if (!nameVar.isString()) { + return nullptr; + } + + auto name = nameVar.toString(); + + auto versionVar = config["pluginVersion"]; + if (!nameVar.isString()) { + return nullptr; + } + + auto version = versionVar.toString(); + + auto filenameVar = config["pluginFilename"]; + if (!filenameVar.isString()) { + return nullptr; + } + + auto filename = filenameVar.toString(); + + return std::shared_ptr(new DPlugin(name, filename, version)); +} + +DPlugin::DPlugin(const QString &name, const QString &filename, const QString &version, QObject *parent) + : QObject(parent) + , library(new QLibrary(filename, version)) + , d_ptr(new DPluginPrivate(name, this)) +{ +} + +QString DPlugin::name() +{ + Q_D(DPlugin); + return d->name; +} + +bool DPluginPrivate::configCompatible(const QJsonValueRef &version) +{ + return version.isString() && version.toString() == "1"; +} + +DPluginPrivate::DPluginPrivate(const QString &name, DPlugin *parent) + : QObject(parent) + , name(name) +{ +} + +DCORE_END_NAMESPACE diff --git a/src/util/dplugin_p.h b/src/util/dplugin_p.h new file mode 100644 index 00000000..ad331439 --- /dev/null +++ b/src/util/dplugin_p.h @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef DPLUGIN_P_H +#define DPLUGIN_P_H + +#include "dplugin.h" + +#include + +DCORE_BEGIN_NAMESPACE + +class DPlugin; + +class DPluginPrivate : public QObject +{ + Q_OBJECT; + +public: + explicit DPluginPrivate(const QString &name, DPlugin *parent); + const QString name; + + static QList> searchPluginsInPath(const QString &pluginDirPath); + static bool configCompatible(const QJsonValueRef &version); + static std::shared_ptr pluginFromJsonContent(const QByteArray &content); + + DPlugin *q_ptr; + Q_DECLARE_PUBLIC(DPlugin); +}; + +DCORE_END_NAMESPACE + +#endif diff --git a/src/util/util.cmake b/src/util/util.cmake index 66b9e3d7..4288667f 100644 --- a/src/util/util.cmake +++ b/src/util/util.cmake @@ -1,44 +1,28 @@ +set(UTILS_SOURCE + ${CMAKE_CURRENT_LIST_DIR}/dabstractunitformatter.cpp + ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedabstractinterface.cpp + ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedpendingcallwatcher.cpp + ${CMAKE_CURRENT_LIST_DIR}/ddbusinterface.cpp + ${CMAKE_CURRENT_LIST_DIR}/ddbussender.cpp + ${CMAKE_CURRENT_LIST_DIR}/ddisksizeformatter.cpp + ${CMAKE_CURRENT_LIST_DIR}/dexportedinterface.cpp + ${CMAKE_CURRENT_LIST_DIR}/dnotifysender.cpp + ${CMAKE_CURRENT_LIST_DIR}/dpinyin.cpp + ${CMAKE_CURRENT_LIST_DIR}/dplugin.cpp + ${CMAKE_CURRENT_LIST_DIR}/drecentmanager.cpp + ${CMAKE_CURRENT_LIST_DIR}/dtextencoding.cpp + ${CMAKE_CURRENT_LIST_DIR}/dthreadutils.cpp + ${CMAKE_CURRENT_LIST_DIR}/dtimedloop.cpp + ${CMAKE_CURRENT_LIST_DIR}/dtimeunitformatter.cpp + ${CMAKE_CURRENT_LIST_DIR}/dvtablehook.cpp +) + if(LINUX) - set(UTILS_SOURCE - ${CMAKE_CURRENT_LIST_DIR}/dtimeunitformatter.cpp - ${CMAKE_CURRENT_LIST_DIR}/dabstractunitformatter.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddisksizeformatter.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddbussender.cpp - ${CMAKE_CURRENT_LIST_DIR}/drecentmanager.cpp - ${CMAKE_CURRENT_LIST_DIR}/dnotifysender.cpp - ${CMAKE_CURRENT_LIST_DIR}/dpinyin.cpp - ${CMAKE_CURRENT_LIST_DIR}/dexportedinterface.cpp - ${CMAKE_CURRENT_LIST_DIR}/dvtablehook.cpp - ${CMAKE_CURRENT_LIST_DIR}/dthreadutils.cpp - ${CMAKE_CURRENT_LIST_DIR}/dtimedloop.cpp + set(UTILS_SOURCE ${UTILS_SOURCE} ${CMAKE_CURRENT_LIST_DIR}/dfileservices_linux.cpp - ${CMAKE_CURRENT_LIST_DIR}/dexportedinterface.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddbusinterface.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedabstractinterface.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedpendingcallwatcher.cpp - ${CMAKE_CURRENT_LIST_DIR}/dtextencoding.cpp - ) -else() - set(UTILS_SOURCE - ${CMAKE_CURRENT_LIST_DIR}/dtimeunitformatter.cpp - ${CMAKE_CURRENT_LIST_DIR}/dabstractunitformatter.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddisksizeformatter.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddbussender.cpp - ${CMAKE_CURRENT_LIST_DIR}/drecentmanager.cpp - ${CMAKE_CURRENT_LIST_DIR}/dnotifysender.cpp - ${CMAKE_CURRENT_LIST_DIR}/dpinyin.cpp - ${CMAKE_CURRENT_LIST_DIR}/dexportedinterface.cpp - ${CMAKE_CURRENT_LIST_DIR}/dvtablehook.cpp - ${CMAKE_CURRENT_LIST_DIR}/dthreadutils.cpp - ${CMAKE_CURRENT_LIST_DIR}/dtimedloop.cpp - ${CMAKE_CURRENT_LIST_DIR}/dfileservices_dummy.cpp - ${CMAKE_CURRENT_LIST_DIR}/dexportedinterface.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddbusinterface.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedabstractinterface.cpp - ${CMAKE_CURRENT_LIST_DIR}/ddbusextendedpendingcallwatcher.cpp - ${CMAKE_CURRENT_LIST_DIR}/dtextencoding.cpp ) endif() + file(GLOB UTILS_HEADER ${CMAKE_CURRENT_LIST_DIR}/../../include/util/* ${CMAKE_CURRENT_LIST_DIR}/ddbusinterface_p.h