Files

167 lines
5.8 KiB
Python

import atexit
import importlib
import inspect
import logging
import os
import pkgutil
import signal
import sys
from comfy_api.latest import ComfyExtension, io, ui
_CACHED_NODES = None
def _discover_nodes() -> list[type[io.ComfyNode]]:
"""
递归扫描并导入 `nodes` 子包下的所有节点类,统一注册。
- 仅在包级入口进行注册,节点文件不实现扩展入口。
- 严格返回继承自 `io.ComfyNode` 的类列表。
"""
global _CACHED_NODES
if _CACHED_NODES is not None:
return _CACHED_NODES
current_dir = os.path.dirname(os.path.realpath(__file__))
nodes_dir = os.path.join(current_dir, "nodes")
discovered: list[type[io.ComfyNode]] = []
if not os.path.isdir(nodes_dir):
logging.info("[ComfyUI-1hewNodes] 未检测到 nodes 目录,跳过扫描")
_CACHED_NODES = discovered
return discovered
package_name = __name__
# 导入顶层 nodes 包以获取遍历 __path__
try:
nodes_pkg = importlib.import_module(".nodes", package=package_name)
except Exception as exc: # pragma: no cover
logging.error(f"[ComfyUI-1hewNodes] 导入 nodes 包失败: {exc}")
_CACHED_NODES = discovered
return discovered
prefix = f"{package_name}.nodes."
for _, name, _ in pkgutil.walk_packages(nodes_pkg.__path__, prefix=prefix):
# 跳过包级 __init__,按模块导入
try:
module = importlib.import_module(name)
except Exception as exc: # pragma: no cover
logging.error(f"[ComfyUI-1hewNodes] 导入模块失败: {name}, 错误: {exc}")
continue
for _, obj in inspect.getmembers(module, inspect.isclass):
try:
if issubclass(obj, io.ComfyNode) and obj is not io.ComfyNode:
discovered.append(obj)
except Exception:
continue
_CACHED_NODES = discovered
return discovered
class NodesV3Extension(ComfyExtension):
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return _discover_nodes()
async def comfy_entrypoint() -> ComfyExtension:
return NodesV3Extension()
WEB_DIRECTORY = os.path.join(os.path.dirname(os.path.realpath(__file__)), "web")
# 前端静态目录注册,确保浏览器同时加载扩展 JS 与节点 docs
try:
import nodes
extension_dir_name = os.path.basename(os.path.dirname(os.path.realpath(__file__)))
if os.path.isdir(WEB_DIRECTORY):
nodes.EXTENSION_WEB_DIRS[extension_dir_name] = WEB_DIRECTORY
except Exception as exc:
logging.warning(f"[ComfyUI-1hewNodes] 前端静态目录注册跳过: {exc}")
# 全局变量保存监控模块引用
_workflow_watcher = None
_prev_sigint_handler = None
_prev_sigterm_handler = None
# 自动启动工作流监控功能
def _start_workflow_monitor():
"""启动工作流监控功能"""
global _workflow_watcher
try:
# 导入工作流监控模块(从utils/workflow文件夹)
from .utils.workflow import monitor
_workflow_watcher = monitor
# 启动监控
success = monitor.start_workflow_monitor()
if success:
# 注册关闭钩子
_register_shutdown_hooks()
else:
logging.warning("[ComfyUI-1hewNodes] 工作流监控启动失败")
except Exception as e:
logging.error(f"[ComfyUI-1hewNodes] 启动工作流监控时出错:{str(e)}")
def _stop_workflow_monitor():
"""停止工作流监控功能"""
global _workflow_watcher
try:
if _workflow_watcher and _workflow_watcher.is_monitoring():
_workflow_watcher.stop_workflow_monitor()
except Exception as e:
logging.error(f"[ComfyUI-1hewNodes] 停止工作流监控时出错:{str(e)}")
def _register_shutdown_hooks():
"""注册关闭钩子和信号处理"""
# 注册atexit钩子
atexit.register(_stop_workflow_monitor)
# 注册信号处理(仅在主线程中有效)
try:
global _prev_sigint_handler, _prev_sigterm_handler
if hasattr(signal, 'SIGINT'):
_prev_sigint_handler = signal.getsignal(signal.SIGINT)
signal.signal(signal.SIGINT, _signal_handler)
if hasattr(signal, 'SIGTERM'):
_prev_sigterm_handler = signal.getsignal(signal.SIGTERM)
signal.signal(signal.SIGTERM, _signal_handler)
except ValueError:
# 在非主线程中注册信号处理会失败,这是正常的
pass
def _signal_handler(signum, frame):
"""信号处理函数"""
logging.info(f"[ComfyUI-1hewNodes] 接收到信号 {signum},正在停止工作流监控...")
_stop_workflow_monitor()
# 保持默认终止行为或链式调用原处理器
try:
if hasattr(signal, 'SIGINT') and signum == signal.SIGINT:
# 如果原处理器是默认或未设置,则触发默认 KeyboardInterrupt
if _prev_sigint_handler in (signal.SIG_DFL, None, signal.default_int_handler):
raise KeyboardInterrupt
# 如果原处理器不是忽略,则链式调用原处理器
if _prev_sigint_handler is not None and _prev_sigint_handler != signal.SIG_IGN:
_prev_sigint_handler(signum, frame)
elif hasattr(signal, 'SIGTERM') and signum == signal.SIGTERM:
# ``signal.getsignal`` may return SIG_DFL/SIG_IGN (enum sentinels),
# not a callable handler. Preserve explicit ignore; otherwise let
# the surrounding ComfyUI shutdown path continue after cleanup.
if _prev_sigterm_handler is signal.SIG_IGN:
return
if callable(_prev_sigterm_handler):
_prev_sigterm_handler(signum, frame)
except KeyboardInterrupt:
# 让上层按默认流程处理退出
raise
# 自动启动工作流监控
_start_workflow_monitor()