Skip to content

Commit e7f76bb

Browse files
authored
优化插件系统 (#82)
* refactor(plugin): 重构插件管理对话框和插件基类 - 优化插件管理对话框的国际化支持 - 重构插件基类的初始化逻辑和配置管理 - 调整插件上下文结构,增加插件目录和应用目录字段 - 更新.gitignore文件,排除.vscode目录和插件数据库文件 * refactor(plugin): 重构插件管理系统,优化消息处理和线程模型 重构插件管理器核心逻辑,使用单线程事件循环替代多线程模型,优化消息处理性能。增加优雅关闭机制,改进心跳检测和错误处理。统一插件上下文管理接口,提升系统稳定性。 * refactor(CheckUpdateGui): 优化ReleaseFrame日期显示逻辑并修复父组件传递问题 移除不必要的日期标签显示/隐藏操作,修复子组件未正确传递父组件的问题
1 parent 3e66528 commit e7f76bb

File tree

18 files changed

+1090
-377
lines changed

18 files changed

+1090
-377
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,5 @@ toollib/frame.csv
167167
old/
168168
发布流程.txt
169169
src/plugins/*/*.json
170+
src/plugins/*/*.db
171+
.vscode/

src/CheckUpdateGui.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,9 @@ def initUi(self):
8989
row1.addWidget(QLabel(self.release.tag_name))
9090
row1.addItem(QSpacerItem(
9191
20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
92-
self.dateTimeLabel.hide()
9392
if self.release.assets_created_at != "":
9493
self.dateTimeLabel.setText(QDateTime.fromString(
9594
self.release.assets_created_at, "yyyy-MM-ddThh:mm:ssZ").toString("yyyy-MM-dd hh:mm:ss"))
96-
self.dateTimeLabel.show()
9795
row1.addWidget(self.dateTimeLabel)
9896
row1.addItem(QSpacerItem(
9997
20, 20, QSizePolicy.Expanding, QSizePolicy.Minimum))
@@ -337,11 +335,11 @@ def checkUpdate(self, releases: list[ReleaseInfo]):
337335
layout.setSpacing(2)
338336
for release in releases:
339337
frame = ReleaseFrame(
340-
release, self.github.compareVersion(release.tag_name), r_path=self.r_path)
338+
release, self.github.compareVersion(release.tag_name), r_path=self.r_path, parent=self)
341339
layout.addWidget(frame)
342340
frame.downLoadFile.connect(self.github.downloadRelease)
343341
# 底部加一个空白区域
344-
panel = QWidget()
342+
panel = QWidget(self)
345343
panel.setContentsMargins(0, 0, 0, 0)
346344
panel.setFixedHeight(100)
347345
layout.addWidget(panel)

src/gen_plugin.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import os
2+
import sys
3+
from pathlib import Path
4+
5+
6+
def is_valid_class_name(name: str) -> bool:
7+
"""检查是否为有效的Python类名"""
8+
if not name or not isinstance(name, str):
9+
return False
10+
if not name.isidentifier():
11+
return False
12+
if name[0].islower():
13+
return False
14+
return True
15+
16+
17+
def build_py_file_content(name: str) -> str:
18+
"""生成Python文件内容"""
19+
template = f'''
20+
import sys
21+
import os
22+
import msgspec
23+
import zmq
24+
25+
if getattr(sys, "frozen", False): # 检查是否为pyInstaller生成的EXE
26+
application_path = os.path.dirname(sys.executable)
27+
sys.path.append(application_path + "/../../")
28+
print(application_path + "/../../")
29+
else:
30+
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../../")
31+
from mp_plugins import BasePlugin, BaseConfig
32+
from mp_plugins.base.config import *
33+
from mp_plugins.context import AppContext
34+
from mp_plugins.events import *
35+
36+
class {name}Config(BaseConfig):
37+
pass
38+
39+
40+
class {name}(BasePlugin):
41+
def __init__(
42+
self,
43+
) -> None:
44+
super().__init__()
45+
self._context: AppContext
46+
self._config = {name}Config()
47+
48+
def build_plugin_context(self) -> None:
49+
self._plugin_context.name = "{name}"
50+
self._plugin_context.display_name = "{name}"
51+
self._plugin_context.version = "1.0.0"
52+
self._plugin_context.description = "{name}"
53+
self._plugin_context.author = ""
54+
55+
def initialize(self) -> None:
56+
return super().initialize()
57+
58+
def shutdown(self) -> None:
59+
return super().shutdown()
60+
61+
62+
63+
if __name__ == "__main__":
64+
try:
65+
import sys
66+
67+
args = sys.argv[1:]
68+
host = args[0]
69+
port = int(args[1])
70+
plugin = {name}()
71+
# 捕获退出信号,优雅关闭
72+
import signal
73+
74+
def signal_handler(sig, frame):
75+
plugin.stop()
76+
sys.exit(0)
77+
78+
signal.signal(signal.SIGINT, signal_handler)
79+
signal.signal(signal.SIGTERM, signal_handler)
80+
plugin.run(host, port)
81+
except Exception:
82+
pass
83+
'''
84+
return template
85+
86+
87+
if __name__ == "__main__":
88+
print("Building plugin...")
89+
args = sys.argv[1:]
90+
if len(args) != 1:
91+
print("Usage: gen_plugin.py <plugin_name>")
92+
exit(1)
93+
name = args[0]
94+
if not is_valid_class_name(name):
95+
print("Invalid plugin name")
96+
exit(1)
97+
current_path = os.path.dirname(os.path.abspath(__file__))
98+
plugin_path = Path(current_path) / "plugins" / name
99+
plugin_path.mkdir(parents=True, exist_ok=True)
100+
plugin_file = plugin_path / f"{name}.py"
101+
with open(plugin_file, "w", encoding="utf-8") as f:
102+
context = build_py_file_content(name)
103+
f.write(context)
104+
print("gen py file success")

src/main.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ def on_ready_read(socket: QLocalSocket):
7474
socket.disconnectFromServer() # 断开连接
7575

7676

77-
7877
def cli_check_file(file_path: str) -> int:
7978
if not os.path.exists(file_path):
8079
print("ERROR: file not found")
@@ -132,7 +131,8 @@ def cli_check_file(file_path: str) -> int:
132131
else:
133132
if videos.len() <= 0:
134133
evf_evfs_files[ide] = (e, 2)
135-
checksum = ui.checksum_guard.get_checksum(videos[0].evf_video.raw_data)
134+
checksum = ui.checksum_guard.get_checksum(
135+
videos[0].evf_video.raw_data)
136136
if video.checksum != checksum:
137137
evf_evfs_files[ide] = (e, 1)
138138
continue
@@ -160,7 +160,10 @@ def cli_check_file(file_path: str) -> int:
160160
exit_code = cli_check_file(args.check)
161161
sys.exit(exit_code)
162162
env = patch_env()
163-
context = AppContext("Metasweeper", "1.0.0", "元扫雷")
163+
context = AppContext(name="Metasweeper", version="1.0.0", display_name="元扫雷",
164+
plugin_dir=(Path(get_paths()) / "plugins").as_posix(),
165+
app_dir=get_paths()
166+
)
164167
PluginManager.instance().context = context
165168

166169
PluginManager.instance().start(Path(get_paths()) / "plugins", env)
@@ -195,9 +198,8 @@ def cli_check_file(file_path: str) -> int:
195198
hwnd, 0x00000011) else 1/0
196199
ui.enable_screenshot = lambda: ... if SetWindowDisplayAffinity(
197200
hwnd, 0x00000000) else 1/0
198-
201+
app.aboutToQuit.connect(PluginManager.instance().stop)
199202
sys.exit(app.exec_())
200-
PluginManager.instance().stop()
201203
...
202204
# except:
203205
# pass

src/mineSweeperGUI.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
import gameAbout
99
import gameSettings
1010
import gameSettingShortcuts
11-
import captureScreen, mine_num_bar, gameRecordPop
11+
import captureScreen
12+
import mine_num_bar
13+
import gameRecordPop
1214
from CheckUpdateGui import CheckUpdateGui
1315
from githubApi import GitHub, SourceManager
1416
import win32con
@@ -32,6 +34,7 @@
3234
from mp_plugins import PluginManager, PluginContext
3335
from mp_plugins.events import GameEndEvent
3436

37+
3538
class MineSweeperGUI(MineSweeperVideoPlayer):
3639
def __init__(self, MainWindow: MainWindow, args):
3740
self.mainWindow = MainWindow
@@ -80,7 +83,7 @@ def save_evf_file_integrated():
8083
lambda: self.trans_language("pl_PL"))
8184
self.german_action.triggered.connect(
8285
lambda: self.trans_language("de_DE"))
83-
86+
8487
# 查看菜单
8588
self.action_open_replay.triggered.connect(
8689
lambda: QDesktopServices.openUrl(
@@ -253,7 +256,8 @@ def game_state(self, game_state: str):
253256
"column": self.column,
254257
"minenum": self.minenum,
255258
})
256-
self.score_board_manager.show(self.label.ms_board, index_type=1)
259+
self.score_board_manager.show(
260+
self.label.ms_board, index_type=1)
257261
case "study":
258262
self.num_bar_ui.QWidget.close()
259263
self._game_state = game_state
@@ -292,7 +296,7 @@ def minenum(self, minenum):
292296
self._minenum = minenum
293297

294298
def layMine(self, i, j):
295-
299+
296300
xx = self.row
297301
yy = self.column
298302
num = self.minenum
@@ -301,13 +305,13 @@ def layMine(self, i, j):
301305
if self.gameMode == 5 or self.gameMode == 6 or self.gameMode == 9:
302306
# 根据模式生成局面
303307
Board, _ = utils.laymine_solvable(self.board_constraint,
304-
self.attempt_times_limit, (xx, yy, num, i, j))
308+
self.attempt_times_limit, (xx, yy, num, i, j))
305309
elif self.gameMode == 0 or self.gameMode == 7 or self.gameMode == 8 or self.gameMode == 10:
306310
Board, _ = utils.laymine(self.board_constraint,
307-
self.attempt_times_limit, (xx, yy, num, i, j))
311+
self.attempt_times_limit, (xx, yy, num, i, j))
308312
elif self.gameMode == 4:
309313
Board, _ = utils.laymine_op(self.board_constraint,
310-
self.attempt_times_limit, (xx, yy, num, i, j))
314+
self.attempt_times_limit, (xx, yy, num, i, j))
311315

312316
self.label.ms_board.board = Board
313317

@@ -362,22 +366,22 @@ def ai(self, i, j):
362366
self.label.ms_board.board = board
363367
elif code == 2:
364368
board, flag = utils.enumerateChangeBoard(self.label.ms_board.board,
365-
self.label.ms_board.game_board, [(i, j)])
369+
self.label.ms_board.game_board, [(i, j)])
366370
self.label.ms_board.board = board
367371
return
368372
elif self.gameMode == 8:
369373
code = ms.is_guess_while_needless(
370374
self.label.ms_board.game_board, (i, j))
371375
if code == 2:
372376
board, flag = utils.enumerateChangeBoard(self.label.ms_board.board,
373-
self.label.ms_board.game_board, [(i, j)])
377+
self.label.ms_board.game_board, [(i, j)])
374378
self.label.ms_board.board = board
375379
return
376380
elif self.gameMode == 9 or self.gameMode == 10:
377381
if self.label.ms_board.board[i][j] == -1:
378382
# 可猜调整的核心逻辑
379383
board, flag = utils.enumerateChangeBoard(self.label.ms_board.board,
380-
self.label.ms_board.game_board, [(i, j)])
384+
self.label.ms_board.game_board, [(i, j)])
381385

382386
self.label.ms_board.board = board
383387
return
@@ -435,8 +439,8 @@ def chording_ai(self, i, j):
435439
break
436440
if must_guess:
437441
board, flag = utils.enumerateChangeBoard(board,
438-
self.label.ms_board.game_board,
439-
not_mine_round + is_mine_round)
442+
self.label.ms_board.game_board,
443+
not_mine_round + is_mine_round)
440444
self.label.ms_board.board = board
441445
else:
442446
for (x, y) in is_mine_round + not_mine_round:
@@ -452,13 +456,13 @@ def chording_ai(self, i, j):
452456
break
453457
if must_guess:
454458
board, flag = utils.enumerateChangeBoard(board,
455-
self.label.ms_board.game_board,
456-
not_mine_round + is_mine_round)
459+
self.label.ms_board.game_board,
460+
not_mine_round + is_mine_round)
457461
self.label.ms_board.board = board
458462
elif self.gameMode == 9 or self.gameMode == 10:
459463
board, flag = utils.enumerateChangeBoard(board,
460-
self.label.ms_board.game_board,
461-
not_mine_round + is_mine_round)
464+
self.label.ms_board.game_board,
465+
not_mine_round + is_mine_round)
462466
self.label.ms_board.board = board
463467

464468
def mineNumWheel(self, i):
@@ -688,7 +692,8 @@ def save_evfs_file(self):
688692
if self.old_evfs_filename:
689693
file_name = self.old_evfs_filename + str(self.evfs.len())
690694
self.evfs.save_evfs_file(file_name)
691-
old_evfs_filename = self.old_evfs_filename + str(self.evfs.len() - 1) + ".evfs"
695+
old_evfs_filename = self.old_evfs_filename + \
696+
str(self.evfs.len() - 1) + ".evfs"
692697
if os.path.exists(old_evfs_filename):
693698
# 进一步确认是文件而不是目录
694699
if os.path.isfile(old_evfs_filename):
@@ -928,13 +933,13 @@ def try_append_evfs(self, new_game_state):
928933
# self.evfs[0].checksum
929934
checksum = self.checksum_guard.get_checksum(
930935
self.label.ms_board.raw_data)
931-
self.evfs.push(self.label.ms_board.raw_data,
936+
self.evfs.push(self.label.ms_board.raw_data,
932937
self.cal_evf_filename(absolute=False), checksum)
933938
else:
934939
evfs_len = self.evfs.len()
935940
checksum = self.checksum_guard.get_checksum(
936941
self.label.ms_board.raw_data + self.evfs[evfs_len - 1].checksum)
937-
self.evfs.push(self.label.ms_board.raw_data,
942+
self.evfs.push(self.label.ms_board.raw_data,
938943
self.cal_evf_filename(absolute=False), checksum)
939944
self.evfs.generate_evfs_v0_raw_data()
940945
self.save_evfs_file()
@@ -1479,6 +1484,5 @@ def closeEvent_(self):
14791484
self.record_setting.sync()
14801485

14811486
def action_OpenPluginDialog(self):
1482-
contexts = list(PluginManager.instance().plugin_contexts)
1483-
dialog = PluginManagerUI(contexts)
1487+
dialog = PluginManagerUI(PluginManager.instance().Get_Plugin_Names())
14841488
dialog.exec()

src/mp_plugins/base/config.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ class SelectSetting(BaseSetting):
3737

3838
class BaseConfig(_BaseData):
3939
""" """
40-
4140
pass
4241

4342

src/mp_plugins/base/context.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class BaseContext(_BaseData):
1111

1212
name: str = ""
1313
version: str = ""
14+
plugin_dir: str = ""
15+
app_dir: str = ""
1416

1517

1618
class PluginContext(BaseContext):

src/mp_plugins/base/mode.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ def __eq__(self, value: object) -> bool:
77
return self.value == value.value
88
return self.value == value # 支持直接比较 value
99

10+
def __str__(self) -> str:
11+
return self.name
12+
1013

1114
class PluginStatus(ValueEnum):
1215
"""

0 commit comments

Comments
 (0)