WingHexExplorer2/src/plugin/pluginsystem.h

416 lines
12 KiB
C++

/*==============================================================================
** Copyright (C) 2024-2027 WingSummer
**
** This program is free software: you can redistribute it and/or modify it under
** the terms of the GNU Affero General Public License as published by the Free
** Software Foundation, version 3.
**
** This program 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 Affero General Public License for more
** details.
**
** You should have received a copy of the GNU Affero General Public License
** along with this program. If not, see <https://www.gnu.org/licenses/>.
** =============================================================================
*/
#ifndef PLUGINSYSTEM_H
#define PLUGINSYSTEM_H
#include <QDockWidget>
#include <QHash>
#include <QList>
#include <QMenu>
#include <QMutex>
#include <QObject>
#include <QTimer>
#include <QTimerEvent>
#include <QToolButton>
#include <QVariant>
#include "class/clickcallback.h"
#include "class/wingangelapi.h"
#include "control/editorview.h"
#include "plugin/iwingdevice.h"
using namespace WingHex;
class MainWindow;
class asCScriptEngine;
class PluginSystem : public QObject {
Q_OBJECT
private:
class UniqueIdGenerator {
public:
class UniqueId : public QSharedData {
public:
UniqueId() : _id(-1), _gen(nullptr) {}
UniqueId(UniqueIdGenerator *gen, int id) : _id(id), _gen(gen) {
Q_ASSERT(gen);
Q_ASSERT(id >= 0);
#ifdef QT_DEBUG
qDebug() << "[UniqueId] alloced: " << id;
#endif
}
~UniqueId() {
if (_gen) {
_gen->release(_id);
}
#ifdef QT_DEBUG
qDebug() << "[UniqueId] freed: " << _id;
#endif
}
operator int() { return _id; }
private:
int _id;
UniqueIdGenerator *_gen;
};
public:
UniqueIdGenerator() : currentId(0) {}
QExplicitlySharedDataPointer<UniqueId> get() {
if (!releasedIds.isEmpty()) {
int id = releasedIds.dequeue();
return QExplicitlySharedDataPointer<UniqueId>(
new UniqueId(this, id));
} else {
return QExplicitlySharedDataPointer<UniqueId>(
new UniqueId(this, currentId++));
}
}
void release(int id) {
if (id < currentId && !releasedIds.contains(id)) {
releasedIds.enqueue(id);
}
}
private:
int currentId;
QQueue<int> releasedIds;
};
using SharedUniqueId =
QExplicitlySharedDataPointer<UniqueIdGenerator::UniqueId>;
public:
struct PluginInfo {
QString id;
QVersionNumber version;
QString vendor;
QList<WingDependency> dependencies;
QString author;
QString license;
QString url;
};
enum class PluginStatus {
Valid,
BrokenVersion,
InvalidID,
DupID,
LackDependencies
};
Q_ENUM(PluginStatus)
private:
struct PluginFileContext {
SharedUniqueId fid;
EditorView *view = nullptr;
IWingPlugin *linkedplg = nullptr;
QUndoCommand *cmd = nullptr;
~PluginFileContext() {
if (cmd) {
delete cmd;
}
}
};
public:
static PluginSystem &instance();
void setMainWindow(MainWindow *win);
QWidget *mainWindow() const;
void loadAllPlugin();
void unloadAllPlugin();
void destory();
const QList<IWingPlugin *> &plugins() const;
IWingPlugin *plugin(qsizetype index) const;
const QList<IWingDevice *> &devices() const;
IWingDevice *device(qsizetype index) const;
WingAngelAPI *angelApi() const;
void cleanUpEditorViewHandle(EditorView *view);
bool dispatchEvent(IWingPlugin::RegisteredEvent event,
const QVariantList &params);
IWingDevice *ext2Device(const QString &ext);
QStringList scriptMarcos() const;
public:
void scriptPragmaBegin();
public:
PluginInfo getPluginInfo(IWingPluginBase *plg) const;
QString getPluginID(IWingPluginBase *plg) const;
static QString getPUID(IWingPluginBase *p);
static QString type2AngelScriptString(IWingPlugin::MetaType type,
bool isArg, bool noModifier = false);
private:
void loadExtPlugin();
void loadDevicePlugin();
void checkDirRootSafe(const QDir &dir);
template <typename T>
std::optional<PluginInfo> loadPlugin(const QFileInfo &filename,
const QDir &setdir);
bool closeEditor(IWingPlugin *plg, int handle, bool force);
bool closeHandle(IWingPlugin *plg, int handle);
bool checkPluginCanOpenedFile(IWingPlugin *plg);
bool checkPluginHasAlreadyOpened(IWingPlugin *plg, EditorView *view);
EditorView *getCurrentPluginView(IWingPlugin *plg);
EditorView *handle2EditorView(IWingPlugin *plg, int handle);
SharedUniqueId assginHandleForPluginView(IWingPlugin *plg,
EditorView *view);
static bool equalCompareHandle(const SharedUniqueId &id, int handle);
static int getUIDHandle(const SharedUniqueId &id);
private:
PluginInfo parsePluginMetadata(const QJsonObject &meta);
PluginStatus checkPluginMetadata(const PluginInfo &meta, bool isPlg);
static bool isValidIdentifier(const QString &str);
void retranslateMetadata(IWingPluginBase *plg, PluginInfo &meta);
private:
void registerFns(IWingPlugin *plg);
void registerUnSafeFns(IWingPlugin *plg);
void registerEnums(IWingPlugin *plg);
void registerMarcos(IWingPlugin *plg);
void registerEvents(IWingPlugin *plg);
static QString getScriptFnSig(const QString &fnName,
const IWingPlugin::ScriptFnInfo &fninfo);
bool isPluginLoaded(const WingDependency &d);
bool isPluginLoaded(const QString &id);
bool checkThreadAff();
static QString packLogMessage(const char *header, const QString &msg);
EditorView *pluginCurrentEditor(IWingPlugin *plg) const;
std::optional<PluginSystem::PluginFileContext>
pluginContextById(IWingPlugin *plg, int fid) const;
std::optional<QVector<PluginFileContext>::iterator>
pluginContextByIdIt(IWingPlugin *plg, int fid);
QUndoCommand *pluginCurrentUndoCmd(IWingPlugin *plg) const;
private:
void loadPlugin(IWingPlugin *p, PluginInfo &meta,
const std::optional<QDir> &setdir);
void loadPlugin(IWingDevice *p, PluginInfo &meta,
const std::optional<QDir> &setdir);
private:
void connectInterface(IWingPlugin *plg);
void connectLoadingInterface(IWingPlugin *plg);
void connectBaseInterface(IWingPlugin *plg);
void connectReaderInterface(IWingPlugin *plg);
void connectControllerInterface(IWingPlugin *plg);
void connectUIInterface(IWingPlugin *plg);
private:
void connectInterface(IWingDevice *plg);
void connectLoadingInterface(IWingDevice *plg);
void registerMarcoDevice(IWingDevice *plg);
private:
void connectBaseInterface(IWingPluginBase *plg);
void connectUIInterface(IWingPluginBase *plg);
void registerPluginDockWidgets(IWingPluginBase *p);
void registerPluginPages(IWingPluginBase *p);
public:
bool updateTextList_API(const QStringList &data, const QString &title,
const ClickCallBack &click,
const ClickCallBack &dblclick);
bool updateTextTree_API(const QString &json, const QString &title,
const ClickCallBack &click,
const ClickCallBack &dblclick);
bool updateTextTable_API(const QString &json, const QStringList &headers,
const QStringList &headerNames,
const QString &title, const ClickCallBack &click,
const ClickCallBack &dblclick);
public:
// fpr crash checking
QString currentLoadingPlugin() const;
private:
template <typename T>
T readBasicTypeContent(IWingPlugin *plg, qsizetype offset) {
Q_STATIC_ASSERT(std::is_integral_v<T> || std::is_floating_point_v<T>);
auto e = pluginCurrentEditor(plg);
if (e) {
_rwlock.lockForRead();
auto buffer = e->hexEditor()->document()->read(offset, sizeof(T));
if (buffer.size() == sizeof(T)) {
auto pb = reinterpret_cast<const T *>(buffer.constData());
_rwlock.unlock();
return *pb;
}
}
if constexpr (std::is_floating_point_v<T>) {
return qQNaN();
} else {
return T(0);
}
}
template <typename T>
bool insertBasicTypeContent(IWingPlugin *plg, qsizetype offset,
const T &value) {
Q_STATIC_ASSERT(std::is_integral_v<T> || std::is_floating_point_v<T>);
auto e = getCurrentPluginView(plg);
if (e) {
auto editor = e->hexEditor();
auto doc = editor->document();
auto buffer = reinterpret_cast<const char *>(&value);
auto uc = pluginCurrentUndoCmd(plg);
auto cmd = doc->MakeInsert(uc, editor->cursor(), offset,
QByteArray(buffer, sizeof(T)));
if (uc == nullptr && cmd) {
_rwlock.lockForWrite();
doc->pushMakeUndo(cmd);
_rwlock.unlock();
return true;
}
}
return false;
}
template <typename T>
bool writeBasicTypeContent(IWingPlugin *plg, qsizetype offset,
const T &value) {
Q_STATIC_ASSERT(std::is_integral_v<T> || std::is_floating_point_v<T>);
auto e = getCurrentPluginView(plg);
if (e) {
auto editor = e->hexEditor();
auto doc = editor->document();
auto buffer = reinterpret_cast<const char *>(&value);
auto uc = pluginCurrentUndoCmd(plg);
auto cmd = doc->MakeReplace(uc, editor->cursor(), offset,
QByteArray(buffer, sizeof(T)));
if (uc == nullptr && cmd) {
_rwlock.lockForWrite();
doc->pushMakeUndo(cmd);
_rwlock.unlock();
return true;
}
}
return false;
}
template <typename T>
bool appendBasicTypeContent(IWingPlugin *plg, const T &value) {
Q_STATIC_ASSERT(std::is_integral_v<T> || std::is_floating_point_v<T>);
auto e = getCurrentPluginView(plg);
if (e) {
auto editor = e->hexEditor();
auto doc = editor->document();
auto buffer = reinterpret_cast<const char *>(&value);
auto uc = pluginCurrentUndoCmd(plg);
auto cmd = doc->MakeAppend(uc, editor->cursor(),
QByteArray(buffer, sizeof(T)));
if (uc == nullptr && cmd) {
_rwlock.lockForWrite();
doc->pushMakeUndo(cmd);
_rwlock.unlock();
return true;
}
}
return false;
}
signals:
void pluginLoading(const QString &plgName);
private:
PluginSystem(QObject *parent = nullptr);
~PluginSystem();
void initCheckingEngine();
void finalizeCheckingEngine();
private:
MainWindow *_win = nullptr;
QHash<IWingPluginBase *, PluginInfo> _pinfos;
QList<IWingPlugin *> _loadedplgs;
QHash<QWidget *, ads::CDockWidget *> _raisedw;
QStringList _lazyplgs;
QList<IWingDevice *> _loadeddevs;
QMap<IWingPlugin::RegisteredEvent, QList<IWingPlugin *>> _evplgs;
QHash<IWingPlugin *, QVector<PluginFileContext>> m_plgviewMap;
QHash<IWingPlugin *, int> m_plgCurrentfid; // fid
QHash<EditorView *, QList<IWingPlugin *>> m_viewBindings;
UniqueIdGenerator m_idGen;
QHash<QString, QHash<QString, WingAngelAPI::ScriptFnInfo>> _scfns;
WingAngelAPI *_angelplg = nullptr;
asCScriptEngine *_engine = nullptr;
QStringList _scriptMarcos;
QList<IWingPlugin *> _pragmaedPlg;
QReadWriteLock _rwlock;
private:
QString _curLoadingPlg;
};
#endif // PLUGINSYSTEM_H