新增简易的8080面板😊

This commit is contained in:
Falck
2026-04-17 23:15:15 +08:00
parent c38d2f66d1
commit 9d19d09821
465 changed files with 9235 additions and 35285 deletions

View File

@@ -0,0 +1,197 @@
"""热插拔插件 - 运行时加载/卸载/更新插件"""
import sys
import time
import threading
from pathlib import Path
from typing import Any, Optional, Callable
from oss.logger.logger import Log
from oss.plugin.types import Plugin, register_plugin_type
class HotReloadError(Exception):
"""热插拔错误"""
pass
class FileWatcher:
"""文件监听器"""
def __init__(self, watch_dirs: list[str], extensions: list[str], on_change: Callable):
self.watch_dirs = [Path(d) for d in watch_dirs]
self.extensions = extensions
self.on_change = on_change
self._running = False
self._thread: Optional[threading.Thread] = None
self._file_times: dict[str, float] = {}
self._scan_files()
def _scan_files(self):
"""扫描当前文件及其修改时间"""
for watch_dir in self.watch_dirs:
if watch_dir.exists():
for f in watch_dir.rglob("*"):
if f.is_file() and f.suffix in self.extensions:
self._file_times[str(f)] = f.stat().st_mtime
def start(self):
"""开始监听"""
self._running = True
self._thread = threading.Thread(target=self._watch_loop, daemon=True)
self._thread.start()
def stop(self):
"""停止监听"""
self._running = False
if self._thread:
self._thread.join(timeout=5)
def _watch_loop(self):
"""监听循环"""
while self._running:
changed = []
current_files = {}
for watch_dir in self.watch_dirs:
if watch_dir.exists():
for f in watch_dir.rglob("*"):
if f.is_file() and f.suffix in self.extensions:
fpath = str(f)
mtime = f.stat().st_mtime
current_files[fpath] = mtime
# 新文件或修改过
if fpath not in self._file_times:
changed.append(("new", f))
elif mtime > self._file_times[fpath]:
changed.append(("modified", f))
# 检查删除的文件
for fpath in self._file_times:
if fpath not in current_files:
changed.append(("deleted", Path(fpath)))
if changed:
self._file_times = current_files
self.on_change(changed)
time.sleep(1)
class HotReloadPlugin(Plugin):
"""热插拔插件"""
def __init__(self):
self.plugin_loader_instance = None
self.watcher: Optional[FileWatcher] = None
self.watch_dirs: list[str] = []
self.watch_extensions: list[str] = [".py", ".json"]
def init(self, deps: dict = None):
"""初始化"""
pass
def start(self):
"""启动 - 自动开始监听默认目录"""
if not self.watch_dirs:
# 默认监听 store 目录
self.watch_dirs = ["store"]
self.start_watching()
def stop(self):
"""停止"""
if self.watcher:
self.watcher.stop()
def set_plugin_loader(self, plugin_loader):
"""设置插件加载器实例"""
self.plugin_loader_instance = plugin_loader
def set_watch_dirs(self, dirs: list[str]):
"""设置监听目录"""
self.watch_dirs = dirs
def start_watching(self):
"""开始监听文件变化"""
if self.watch_dirs and self.plugin_loader_instance:
self.watcher = FileWatcher(
self.watch_dirs,
self.watch_extensions,
self._on_file_change
)
self.watcher.start()
def _on_file_change(self, changes: list[tuple[str, Path]]):
"""文件变化回调"""
for change_type, fpath in changes:
# 只关心 main.py 和 manifest.json 的变化
if fpath.name not in ("main.py", "manifest.json"):
continue
plugin_dir = fpath.parent
plugin_name = plugin_dir.name
try:
if change_type == "new":
self.load_plugin(plugin_dir)
elif change_type == "modified":
self.reload_plugin(plugin_name, plugin_dir)
elif change_type == "deleted":
self.unload_plugin(plugin_name)
except Exception as e:
Log.error("hot-reload", f"处理变化失败: {e}")
def load_plugin(self, plugin_dir: Path) -> bool:
"""运行时加载插件"""
try:
plugin_name = plugin_dir.name
if plugin_name in self.plugin_loader_instance.plugins:
raise HotReloadError(f"插件已存在: {plugin_name}")
self.plugin_loader_instance.load(plugin_dir)
info = self.plugin_loader_instance.plugins[plugin_name]
instance = info["instance"]
instance.init()
instance.start()
return True
except Exception as e:
raise HotReloadError(f"加载插件失败: {e}")
def unload_plugin(self, plugin_name: str) -> bool:
"""运行时卸载插件"""
try:
if plugin_name not in self.plugin_loader_instance.plugins:
raise HotReloadError(f"插件不存在: {plugin_name}")
info = self.plugin_loader_instance.plugins[plugin_name]
instance = info["instance"]
instance.stop()
# 从模块缓存中移除
module = info.get("module")
if module and module.__name__ in sys.modules:
del sys.modules[module.__name__]
del self.plugin_loader_instance.plugins[plugin_name]
return True
except Exception as e:
raise HotReloadError(f"卸载插件失败: {e}")
def reload_plugin(self, plugin_name: str, plugin_dir: Path) -> bool:
"""运行时更新插件"""
try:
# 先卸载
self.unload_plugin(plugin_name)
# 再加载
return self.load_plugin(plugin_dir)
except Exception as e:
raise HotReloadError(f"更新插件失败: {e}")
# 注册类型
register_plugin_type("HotReloadError", HotReloadError)
register_plugin_type("FileWatcher", FileWatcher)
def New():
return HotReloadPlugin()