docs: add plugin development documentation#1485
docs: add plugin development documentation#1485hualet wants to merge 1 commit intolinuxdeepin:masterfrom
Conversation
Reviewer's GuideAdds a new DDE Shell plugin documentation suite, including bilingual API references and step‑by‑step tutorials for QML, widget, and containment/panel plugins, plus an index README for navigation. Sequence diagram for DDE Shell plugin loading lifecyclesequenceDiagram
participant DDE_Shell
participant DPluginLoader
participant DPluginMetaData
participant DAppletFactory
participant MyPlugin
participant QML_Engine
DDE_Shell->>DPluginLoader: scanPlugins()
DPluginLoader->>DPluginMetaData: fromJsonFile(metadata.json)
DPluginMetaData-->>DPluginLoader: DPluginMetaData
DDE_Shell->>DAppletFactory: create(DPluginMetaData)
DAppletFactory-->>MyPlugin: construct instance
DDE_Shell->>MyPlugin: load()
MyPlugin-->>DDE_Shell: bool loaded
DDE_Shell->>MyPlugin: init()
MyPlugin-->>DDE_Shell: bool initialized
alt QML_based_applet
DDE_Shell->>QML_Engine: load(QML from Url)
QML_Engine-->>MyPlugin: rootObject
else widget_based_applet
MyPlugin->>MyPlugin: create QWidget hierarchy
MyPlugin->>MyPlugin: show()
end
ER diagram for plugin metadata.json structureerDiagram
PluginMetadata ||--|| Plugin : contains
PluginMetadata {
string filePath
string pluginDir
}
Plugin {
string Version
string Id
string Url
string Parent
string ContainmentType
}
Class diagram for DDE Shell plugin core APIsclassDiagram
class DApplet {
+QString id()
+QString pluginId()
+QObject rootObject()
+DApplet parentApplet()
+DPluginMetaData pluginMetaData()
+DAppletData appletData()
+void setAppletData(DAppletData data)
+void setRootObject(QObject root)
+bool load()
+bool init()
+QObject createProxyMeta()
+signal rootObjectChanged()
}
class DContainment {
+DAppletItemModel appletItemModel()
+QList~DApplet~ applets()
+QList~QObject~ appletItems()
+DApplet applet(QString id)
+DApplet createApplet(DAppletData data)
+void removeApplet(DApplet applet)
+bool load()
+bool init()
+QObject createProxyMeta()
}
class DPanel {
+bool load()
+bool init()
}
class DPluginMetaData {
+bool isValid()
+QString pluginId()
+QString pluginDir()
+QString url()
+QVariant value(QString key, QVariant defaultValue)
+static DPluginMetaData fromJsonFile(QString file)
+static DPluginMetaData fromJsonString(QByteArray data)
+static DPluginMetaData rootPluginMetaData()
+static bool isRootPlugin(QString pluginId)
}
class DAppletBridge {
+DAppletBridge(QString pluginId)
+DApplet applet()
}
class DAppletItemModel {
+int rowCount()
+QObject data(int row)
}
DContainment --|> DApplet
DPanel --|> DContainment
DApplet "1" --> "1" DPluginMetaData : uses
DContainment "1" --> "1" DAppletItemModel : exposes
DAppletBridge ..> DApplet : resolves
DContainment "1" o-- "*" DApplet : manages
Flow diagram for DDE Shell plugin development workflowflowchart TD
A[Choose_plugin_type<br/>Applet_or_Containment_or_Panel] --> B[Create_project_structure<br/>sources_and_package_dir]
B --> C[Implement_plugin_class<br/>DApplet_or_DContainment_or_DPanel]
C --> D[Write_metadata.json<br/>Version_Id_Url_ContainmentType]
D --> E[Create_CMakeLists.txt<br/>use_ds_install_package_and_link]
E --> F[Build_plugin<br/>cmake_and_build]
F --> G[Install_plugin<br/>cmake_install]
G --> H[Test_in_DDE_Shell<br/>check_loading_and_UI]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In docs/plugin/README.md the language tags and links are inconsistent: API.md and tutorial.md are actually Chinese content but are labeled as English, and the Chinese docs section references API_zh_CN.md/tutorial_zh_CN.md which are not present—please align filenames, labels, and links with the actual languages you added.
- docs/plugin/README_zh_CN.md is supposed to be the Chinese overview but currently contains English text and references the same English-labeled API.md/tutorial.md; consider providing a real Chinese version or clearly marking this file’s language consistently with the rest of the docs.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In docs/plugin/README.md the language tags and links are inconsistent: API.md and tutorial.md are actually Chinese content but are labeled as English, and the Chinese docs section references API_zh_CN.md/tutorial_zh_CN.md which are not present—please align filenames, labels, and links with the actual languages you added.
- docs/plugin/README_zh_CN.md is supposed to be the Chinese overview but currently contains English text and references the same English-labeled API.md/tutorial.md; consider providing a real Chinese version or clearly marking this file’s language consistently with the rest of the docs.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- Add comprehensive plugin API reference with DApplet, DContainment, and DPanel APIs - Add step-by-step tutorials for QML, Widget, and Containment plugins - Include bilingual documentation (Chinese and English) - Add self-review documents confirming usability
deepin pr auto reviewDDE Shell 插件文档审查报告我已对提供的 DDE Shell 插件文档进行了全面审查,以下是关于语法逻辑、代码质量、代码性能和代码安全的详细分析及改进建议: 1. 语法逻辑审查1.1 文档结构文档整体结构清晰,分为中英文版本,涵盖了API参考、教程和概述,便于不同语言用户查阅。但存在以下问题:
1.2 插件类型定义文档中定义了三种插件类型:Applet Plugin、Containment Plugin和Panel Plugin,但它们之间的继承关系描述可以更清晰: // 当前文档中的描述
class DPanel : public DContainment // DPanel继承自DContainment建议:在文档中添加一个类继承关系图,更直观地展示各插件类型之间的关系: 1.3 API方法定义文档中DApplet类的API定义存在不一致: // 当前文档中的定义
virtual bool load() override;
virtual bool init() override;问题:这些方法被标记为override,但没有明确说明它们重写的是哪个基类的方法。 建议:明确指出这些方法重写自DApplet的基类方法,并添加注释说明它们的用途: // 建议的改进
// 从基类继承的初始化方法,在插件加载时调用
virtual bool load() override; // 重写自DApplet基类
// 从基类继承的初始化方法,在load()之后调用
virtual bool init() override; // 重写自DApplet基类2. 代码质量审查2.1 错误处理文档中的错误处理示例较为简单: bool MyPlugin::init()
{
if (!requiredResourceAvailable()) {
qWarning() << "Required resource not available";
return false;
}
return DApplet::init();
}问题:错误处理不够全面,缺少对资源释放和状态恢复的说明。 建议:添加更完整的错误处理示例,包括资源清理和状态恢复: bool MyPlugin::init()
{
// 保存初始状态,以便在出错时恢复
bool initialState = m_initialized;
// 检查必需资源
if (!requiredResourceAvailable()) {
qWarning() << "Required resource not available";
// 清理已分配的资源
cleanupResources();
// 恢复初始状态
m_initialized = initialState;
return false;
}
// 尝试初始化
if (!DApplet::init()) {
qWarning() << "Base class initialization failed";
cleanupResources();
m_initialized = initialState;
return false;
}
m_initialized = true;
return true;
}
void MyPlugin::cleanupResources()
{
// 释放已分配的资源
if (m_resource) {
delete m_resource;
m_resource = nullptr;
}
}2.2 代码注释文档中的代码示例缺少足够的注释,特别是复杂的逻辑部分。 建议:为关键代码段添加详细注释,解释其功能和实现方式: // 创建主窗口小部件
auto widget = new QWidget();
// 使用DPlatformWindowHandle管理窗口,确保与DDE Shell正确集成
DPlatformWindowHandle handle(widget);
// 设置固定大小,确保UI一致性
widget->setFixedSize(QSize(200, 100));2.3 命名约定文档中提到了ID命名约定,但没有对代码中的变量和方法命名给出明确指导。 建议:添加代码命名约定部分,建议:
3. 代码性能审查3.1 QML性能优化文档中提到了QML性能建议,但较为简略: 问题:缺少具体的性能优化技巧和示例。 建议:添加更详细的QML性能优化指南:
// 不好的做法 - 每次parent改变都会重新计算
property int calculatedSize: parent.width * 0.5
// 好的做法 - 使用缓存和条件更新
property int cachedSize: 0
onParentChanged: {
cachedSize = parent.width * 0.5
}
// 不好的做法 - 所有内容立即加载
Loader {
sourceComponent: heavyComponent
}
// 好的做法 - 按需加载
Loader {
active: someCondition // 只在需要时加载
sourceComponent: heavyComponent
}
// 不好的做法 - 直接连接导致不必要的评估
MyObject {
onSomeSignal: {
// 处理信号
}
}
// 好的做法 - 使用Connections按需连接
Connections {
target: myObject // 只在需要时连接
function onSomeSignal() {
// 处理信号
}
}3.2 C++性能优化文档中缺少C++插件性能优化的指导。 建议:添加C++性能优化部分:
// 不好的做法 - 手动管理内存
QWidget* widget = new QWidget();
// ... 使用widget
delete widget; // 容易忘记或出错
// 好的做法 - 使用智能指针
std::unique_ptr<QWidget> widget = std::make_unique<QWidget>();
// ... 使用widget
// 自动释放内存
// 不好的做法 - 每次调用都分配新内存
void process() {
QByteArray buffer;
buffer.resize(1024); // 每次都重新分配
// ... 使用buffer
}
// 好的做法 - 重用已分配的内存
void MyClass::process() {
if (m_buffer.size() < 1024) {
m_buffer.resize(1024); // 只在必要时分配
}
// ... 使用m_buffer
}
// 不好的做法 - 按值传递大对象
void processLargeObject(QList<QString> data) {
// 处理数据
}
// 好的做法 - 使用const引用
void processLargeObject(const QList<QString>& data) {
// 处理数据
}4. 代码安全审查4.1 插件隔离文档中缺少关于插件隔离和安全沙箱的说明。 问题:插件系统没有明确的安全边界,可能存在安全风险。 建议:添加插件安全隔离部分:
// 在metadata.json中添加权限声明
{
"Plugin": {
"Version": "1.0",
"Id": "org.mycompany.myapplet",
"Permissions": ["network", "filesystem", "dbus"]
}
}
// 在加载插件时检查权限
bool MyPlugin::init()
{
auto metadata = pluginMetaData();
if (!metadata.hasPermission("network")) {
qWarning() << "Plugin does not have network permission";
return false;
}
// ... 初始化插件
}
// 限制插件只能访问特定目录
bool MyPlugin::init()
{
// 设置插件的工作目录
QDir::setCurrent(pluginDir());
// 限制文件访问范围
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert("XDG_DATA_HOME", pluginDir());
// ... 初始化插件
}4.2 输入验证文档中缺少对插件输入验证的说明。 问题:插件可能接收来自不可信来源的数据,需要进行验证。 建议:添加输入验证部分:
bool isValidPluginId(const QString& id)
{
// 检查ID格式
QRegularExpression idPattern("^[a-z]+(\\.[a-z]+)+$");
if (!idPattern.match(id).hasMatch()) {
return false;
}
// 检查ID长度
if (id.length() > 255) {
return false;
}
// 检查ID是否包含非法字符
if (id.contains("..") || id.contains("/")) {
return false;
}
return true;
}
bool isValidQmlPath(const QString& path)
{
// 检查路径是否在插件目录内
QDir pluginDir = pluginMetaData().pluginDir();
QDir absolutePath = QFileInfo(path).absoluteDir();
if (!absolutePath.absolutePath().startsWith(pluginDir.absolutePath())) {
qWarning() << "QML path is outside plugin directory:" << path;
return false;
}
// 检查文件扩展名
if (!path.endsWith(".qml", Qt::CaseInsensitive)) {
qWarning() << "Invalid QML file extension:" << path;
return false;
}
return true;
}4.3 错误处理安全性文档中的错误处理示例不够全面,可能导致安全漏洞。 问题:错误处理不当可能导致信息泄露或资源泄漏。 建议:添加安全的错误处理示例: bool MyPlugin::init()
{
try {
// 检查必需资源
if (!requiredResourceAvailable()) {
// 使用通用错误消息,避免信息泄露
qWarning() << "Failed to initialize plugin: resource unavailable";
return false;
}
// 初始化插件
if (!DApplet::init()) {
qWarning() << "Failed to initialize plugin: base class initialization failed";
return false;
}
return true;
} catch (const std::exception& e) {
// 捕获所有异常,防止插件崩溃
qWarning() << "Exception during plugin initialization:" << e.what();
// 确保资源被正确清理
cleanupResources();
return false;
} catch (...) {
// 捕获未知异常
qWarning() << "Unknown exception during plugin initialization";
cleanupResources();
return false;
}
}5. 其他改进建议5.1 添加版本兼容性指南文档中缺少关于插件版本兼容性的说明。 建议:添加版本兼容性部分,说明如何处理不同版本的DDE Shell: // 在metadata.json中声明兼容的DDE Shell版本
{
"Plugin": {
"Version": "1.0",
"Id": "org.mycompany.myapplet",
"DDEShellVersion": {
"Min": "1.0.0",
"Max": "2.0.0"
}
}
}
// 在插件代码中检查版本兼容性
bool MyPlugin::load()
{
auto metadata = pluginMetaData();
QString shellVersion = metadata.value("DDEShellVersion", "").toString();
if (shellVersion.isEmpty()) {
qWarning() << "No DDE Shell version specified in metadata";
return false;
}
QVersionNumber minVersion = QVersionNumber::fromString(shellVersion.split("-")[0]);
QVersionNumber currentVersion = QVersionNumber::fromString(DDE_SHELL_VERSION);
if (currentVersion < minVersion) {
qWarning() << "DDE Shell version" << currentVersion
<< "is not compatible with this plugin (requires" << minVersion << ")";
return false;
}
return DApplet::load();
}5.2 添加插件测试指南文档中缺少关于插件测试的指导。 建议:添加插件测试部分,包括单元测试和集成测试的示例: // 单元测试示例
void TestMyPlugin::testInit()
{
MyPlugin plugin;
QVERIFY(plugin.init());
QCOMPARE(plugin.pluginId(), QString("org.mycompany.myapplet"));
}
void TestMyPlugin::testErrorHandling()
{
MyPlugin plugin;
// 模拟资源不可用的情况
plugin.setResourceAvailable(false);
QVERIFY(!plugin.init());
}
// QML测试示例
TestCase {
name: "MyPluginTest"
MyPlugin {
id: plugin
}
function test_init() {
verify(plugin.init(), "Plugin should initialize successfully")
compare(plugin.pluginId, "org.mycompany.myapplet", "Plugin ID should match")
}
function test_errorHandling() {
plugin.resourceAvailable = false
verify(!plugin.init(), "Plugin should fail to initialize without resources")
}
}5.3 添加插件调试指南文档中缺少关于插件调试的详细指导。 建议:添加插件调试部分,包括调试技巧和工具:
// 在插件中添加调试日志
bool MyPlugin::init()
{
qDebug() << "Initializing plugin" << pluginId();
// 使用qCDebug添加分类日志
qCDebug(pluginLog) << "Plugin version:" << pluginMetaData().value("Version");
// ... 初始化代码
qCDebug(pluginLog) << "Plugin initialized successfully";
return true;
}
# 在Qt Creator中设置调试环境变量
QT_LOGGING_RULES="dde.shell.plugin.debug=true"
QT_DEBUG_PLUGINS=1
# 启动DDE Shell并附加GDB
gdb --args dde-shell --plugin org.mycompany.myapplet
# 在GDB中设置断点
(gdb) break MyPlugin::init
(gdb) run
# 查看调用栈
(gdb) bt总结DDE Shell插件文档整体结构清晰,内容全面,但在语法逻辑、代码质量、代码性能和代码安全方面仍有改进空间。主要建议包括:
通过实施这些改进,文档将更加全面、实用,帮助开发者创建更高质量、更安全、更高效的DDE Shell插件。 |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: 18202781743, hualet The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Summary by Sourcery
Add comprehensive bilingual documentation for developing DDE Shell plugins, including tutorials and API references.
Documentation: