新增简易的8080面板😊
This commit is contained in:
@@ -1,196 +0,0 @@
|
||||
"""热插拔插件 - 运行时加载/卸载/更新插件"""
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Callable
|
||||
|
||||
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:
|
||||
print(f"[hot-reload] 处理变化失败: {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()
|
||||
Reference in New Issue
Block a user