Files
RiceRound-ComfyUI_RiceRound/rice_install_client.py
T
2025-02-21 00:11:09 +08:00

313 lines
14 KiB
Python

import os
import shutil
import sys
import tempfile
import time
import portalocker
import requests
import tomlkit
from tomlkit import comment, table, dumps
import subprocess
from .rice_def import RiceRoundErrorDef
from .rice_url_config import RiceUrlConfig
from .auth_unit import AuthUnit
from .rice_prompt_info import RiceEnvConfig
from .utils import get_local_app_setting_path
class RiceInstallClient:
def __init__(self):
self.current_path = os.path.dirname(os.path.abspath(__file__))
self.app_path = get_local_app_setting_path()
(
self.source_executable_filename,
self.executable_filename,
) = self._get_platform_executables()
def _get_platform_executables(self):
"Get platform-specific executable filenames."
if sys.platform == "win32":
return "share_client_windows.exe", "share_client.exe"
elif sys.platform == "darwin":
return "share_client_mac", "share_client"
elif sys.platform == "linux":
return "share_client_linux", "share_client"
else:
raise OSError(f"Unsupported platform: {sys.platform}")
def is_client_running(self):
if not self.is_client_installed():
return False
lock_path = os.path.join(tempfile.gettempdir(), "rice_client.lock")
if not os.path.exists(lock_path):
return False
try:
with open(lock_path, "w") as f:
portalocker.lock(f, portalocker.LOCK_EX | portalocker.LOCK_NB)
portalocker.unlock(f)
return False
except portalocker.LockException:
return True
def is_client_installed(self):
if not self.app_path.exists():
return False
executable_path = self.app_path / self.executable_filename
if not executable_path.exists():
return False
toml_path = self.app_path / "client.toml"
if not toml_path.exists():
return False
return True
def repair_client_toml(self, client_toml_path):
if not client_toml_path.exists():
return False
try:
env_config = RiceEnvConfig().read_env()
if not env_config:
print("Error: Failed to read environment config")
return False
if not env_config.get("PythonPath") or not os.path.exists(
env_config["PythonPath"]
):
print("Error: Invalid Python path in environment config")
return False
env_working_dir = env_config.get("WorkingDirectory")
env_script_name = env_config.get("ScriptName")
if not env_working_dir or not env_script_name:
print(
"Error: Missing working directory or script name in environment config"
)
return False
if os.path.isabs(env_script_name) and os.path.exists(env_script_name):
env_script_path = env_script_name
else:
env_script_path = os.path.join(env_working_dir, env_script_name)
if not os.path.exists(env_script_path):
print("Error: ComfyUI script path does not exist in environment config")
return False
with open(client_toml_path, "r", encoding="utf-8") as f:
toml_data = tomlkit.load(f)
comfyui_config = toml_data.get("ComfyUI")
if not comfyui_config or not isinstance(comfyui_config, dict):
return False
script_name = comfyui_config.get("ComfyuiScriptName")
working_dir = comfyui_config.get("WorkingDirectory")
if working_dir is None or script_name is None:
comfyui_config["WorkingDirectory"] = env_config["WorkingDirectory"]
comfyui_config["ComfyuiScriptName"] = env_config["ScriptName"]
else:
if os.path.isabs(script_name) and os.path.exists(script_name):
script_path = script_name
else:
script_path = os.path.join(working_dir, script_name)
if not os.path.exists(script_path):
comfyui_config["WorkingDirectory"] = env_config[
"WorkingDirectory"
]
comfyui_config["ComfyuiScriptName"] = env_config["ScriptName"]
python_path = comfyui_config.get("PythonPath")
if python_path is None or not os.path.exists(python_path):
comfyui_config["PythonPath"] = env_config["PythonPath"]
with open(client_toml_path, "w", encoding="utf-8") as f:
f.write(dumps(toml_data))
return True
except Exception as e:
print(f"Error repairing client.toml: {str(e)}")
return False
def install_client_toml(self, comfyui_port, local_server_port, secret_token):
env_config = RiceEnvConfig().read_env()
os.makedirs(self.app_path, exist_ok=True)
config = tomlkit.document()
config.add(comment("日志级别设置"))
config.add(comment("可选值: 'debug', 'info', 'warn', 'error'"))
config["LogLevel"] = "info"
config.add(comment("机器码,非常重要,用于登录鉴权"))
config.add(comment("在官网可以获取自己的机器码,普通用户也可以由管理员授予"))
config["SecretToken"] = secret_token
config.add(comment("本地服务端口"))
config.add(comment("用于本地服务端口,通常为 6608"))
config["Port"] = local_server_port
comfyui_table = table()
comfyui_table.add(comment("ComfyUI 监听的端口"))
comfyui_table.add(comment("端口号,默认为 6607"))
comfyui_table["Port"] = comfyui_port
comfyui_table.add(comment("Python 解释器路径"))
comfyui_table.add(comment("这里填写你安装的 Python 解释器路径,确保 Python 环境已经配置好"))
comfyui_table["PythonPath"] = str(env_config["PythonPath"])
comfyui_table.add(comment("ComfyUI 脚本的文件名"))
comfyui_table.add(comment("这里填写 ComfyUI 的启动脚本名,通常是 'main.py'"))
comfyui_table["ComfyuiScriptName"] = env_config["ScriptName"]
comfyui_table.add(comment("ComfyUI 工作目录"))
comfyui_table.add(comment("这里填写 ComfyUI 所在的目录路径"))
comfyui_table["WorkingDirectory"] = str(env_config["WorkingDirectory"])
comfyui_table.add(comment("环境命令,用于激活相关环境"))
comfyui_table.add(comment("例如可以填写 conda 环境的激活命令 conda activate comfyui"))
comfyui_table["EnvCmd"] = ""
comfyui_table.add(comment("启动时附加的命令行参数"))
comfyui_table.add(comment("可根据需要添加,常用的如 '--disable-metadata'"))
comfyui_table["AddCmd"] = env_config["AddCmd"]
config["ComfyUI"] = comfyui_table
try:
client_toml_path = self.app_path / "client.toml"
with open(client_toml_path, "w", encoding="utf-8") as f:
f.write(dumps(config))
return True
except Exception as e:
print(f"Error writing client.toml: {str(e)}")
return False
def install_client_executable(self):
try:
souce_executable_path = os.path.join(
self.current_path, self.source_executable_filename
)
target_executable_path = os.path.join(
self.app_path, self.executable_filename
)
if os.path.exists(target_executable_path):
return True
if not os.path.exists(souce_executable_path):
raise FileNotFoundError(
f"Executable file not found: {souce_executable_path}"
)
shutil.copyfile(souce_executable_path, target_executable_path)
if sys.platform == "linux":
os.chmod(target_executable_path, 493)
return True
except Exception as e:
print(f"Error installing client executable: {str(e)}")
return False
def run_client(self, comfyui_port=6607, local_server_port=6608):
if self.is_client_running():
return RiceRoundErrorDef.SUCCESS, ""
executable_path = self.app_path / self.executable_filename
if not executable_path.exists():
self.install_client_executable()
toml_path = self.app_path / "client.toml"
if not toml_path.exists():
secret_token, error_message, error_code = self.get_secret_token()
if not secret_token:
return (
error_code
if error_code != RiceRoundErrorDef.SUCCESS
else RiceRoundErrorDef.ERROR_SECRET_TOKEN,
error_message,
)
if not self.install_client_toml(
comfyui_port, local_server_port, secret_token
):
return RiceRoundErrorDef.ERROR_INSTALL_CLIENT_TOML, "安装client.toml失败"
else:
self.repair_client_toml(toml_path)
error_code, error_message = self.start_client()
return error_code, error_message
def start_client(self):
executable_path = self.app_path / self.executable_filename
if not executable_path.exists():
return RiceRoundErrorDef.ERROR_EXECUTABLE_NOT_FOUND, "找不到可执行文件"
try:
if sys.platform == "win32":
flags = (
subprocess.DETACHED_PROCESS
| subprocess.CREATE_NEW_PROCESS_GROUP
| subprocess.CREATE_BREAKAWAY_FROM_JOB
)
proc = subprocess.Popen(
str(executable_path),
shell=True,
start_new_session=True,
creationflags=flags,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
close_fds=True,
)
else:
proc = subprocess.Popen(
str(executable_path),
shell=False,
start_new_session=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
close_fds=True,
)
time.sleep(10)
if proc.poll() is not None:
return RiceRoundErrorDef.ERROR_PROCESS_EXIT, "程序启动后异常退出"
else:
return RiceRoundErrorDef.SUCCESS, ""
except Exception as e:
print(f"程序启动异常: {str(e)}")
return RiceRoundErrorDef.ERROR_START_EXCEPTION, str(e)
def get_secret_token(self):
token, error_message, error_code = AuthUnit().get_user_token()
if not token:
return None, error_message, error_code
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}",
}
try:
response = requests.get(
RiceUrlConfig().machine_bind_key_url, headers=headers, timeout=10
)
if response.status_code != 200:
error_code = response.status_code
return (
None,
"获取密钥失败",
RiceRoundErrorDef.calc_error_code(
RiceRoundErrorDef.ERROR_MACHINE_CODE_BASE, error_code
),
)
response_data = response.json()
if response_data.get("code") != 0:
return None, "获取密钥失败: 响应码不为0", RiceRoundErrorDef.ERROR_SECRET_TOKEN
secret_token = response_data.get("data", {}).get("key")
if not secret_token:
return None, "获取密钥失败: 密钥为空", RiceRoundErrorDef.ERROR_SECRET_TOKEN
return secret_token, "", 0
except Exception as e:
return None, "获取密钥失败" + str(e), RiceRoundErrorDef.ERROR_SECRET_TOKEN
def restart_client(self):
executable_path = self.app_path / self.executable_filename
if not executable_path.exists():
return RiceRoundErrorDef.ERROR_EXECUTABLE_NOT_FOUND, "找不到可执行文件"
try:
import subprocess
if sys.platform == "win32":
flags = subprocess.DETACHED_PROCESS | subprocess.CREATE_NO_WINDOW
proc = subprocess.Popen(
[str(executable_path), "--reload"],
shell=False,
creationflags=flags,
start_new_session=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
close_fds=True,
)
else:
proc = subprocess.Popen(
[str(executable_path), "--reload"],
shell=False,
start_new_session=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
close_fds=True,
)
return RiceRoundErrorDef.SUCCESS, ""
except Exception as e:
print(f"重启程序异常: {str(e)}")
return RiceRoundErrorDef.ERROR_RESTART_EXCEPTION, str(e)