352 lines
11 KiB
Python
352 lines
11 KiB
Python
|
|
import os
|
|
import re
|
|
import sys
|
|
import subprocess
|
|
import folder_paths
|
|
import shutil
|
|
import json
|
|
import signal
|
|
import psutil
|
|
from aiohttp import web
|
|
import atexit
|
|
import threading
|
|
import logging
|
|
import time
|
|
# import traceback
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class WebBuilder:
|
|
NUXT_PID_ENV = "NUXT_PID"
|
|
web_process = None
|
|
install_started = False
|
|
generated_message = (
|
|
"Do not edit. "
|
|
"Generated from presentation-ComfyUI. "
|
|
"Will be overwritten when someone changes the workflow."
|
|
)
|
|
|
|
def __init__(self):
|
|
self.dest_path_short = 'presentation-site'
|
|
self.dest_path = os.path.join(
|
|
folder_paths.get_output_directory(),
|
|
self.dest_path_short,
|
|
)
|
|
self.pid_path = os.path.join(
|
|
self.dest_path, 'nuxt.pid'
|
|
)
|
|
self.tabs_path = os.path.join(
|
|
self.dest_path,
|
|
"components/tabs"
|
|
)
|
|
|
|
WebBuilder.exit_handler_done = False
|
|
self.userRequestedStopFile = os.path.join(
|
|
self.dest_path,
|
|
"nuxt.stop",
|
|
)
|
|
|
|
def get_web_process_running(self):
|
|
if os.path.exists(self.pid_path):
|
|
# if WebBuilder.NUXT_PID_ENV in os.environ:
|
|
# pid = int(os.environ[WebBuilder.NUXT_PID_ENV])
|
|
with open(self.pid_path) as p:
|
|
pid = int(p.read())
|
|
if psutil.pid_exists(pid):
|
|
return pid
|
|
else:
|
|
print(f"Has nuxt pid but no process {pid}")
|
|
return None
|
|
|
|
def kill_web_process_running(self):
|
|
pid = self.get_web_process_running()
|
|
if pid is not None:
|
|
pid = int(pid)
|
|
attempts = 0
|
|
while True:
|
|
os.kill(pid, signal.SIGTERM)
|
|
time.sleep(3)
|
|
if not psutil.pid_exists(pid):
|
|
logger.info(f"killed {pid}")
|
|
break
|
|
attempts += 1
|
|
if attempts >= 3:
|
|
logger.warn(f"nuxt pid did not exit: {pid}")
|
|
|
|
return pid
|
|
|
|
def write_file(self, file, s):
|
|
with open(file, "w", encoding="utf-8") as f:
|
|
f.write(s)
|
|
|
|
def get_tabs_path(self):
|
|
if not os.path.exists(self.tabs_path):
|
|
os.mkdir(self.tabs_path)
|
|
return self.tabs_path
|
|
|
|
def make_tab_list(self):
|
|
workflows_list_path = os.path.join(
|
|
self.get_tabs_path(),
|
|
"WorkflowsList.js"
|
|
)
|
|
tab_list_path = os.path.join(
|
|
self.dest_path,
|
|
"components/Tablist.vue"
|
|
)
|
|
|
|
tab_list_orig = ""
|
|
if os.path.exists(tab_list_path):
|
|
with open(tab_list_path) as main:
|
|
tab_list_orig = main.read()
|
|
|
|
files = os.listdir(self.tabs_path)
|
|
tab_classes = []
|
|
tab_classes_html = []
|
|
workflows_js = []
|
|
workflow_class_names = []
|
|
|
|
for file in files:
|
|
if file.endswith(".vue"):
|
|
class_name = re.sub(
|
|
r"\.vue$", "", file
|
|
)
|
|
tab_classes.append(class_name)
|
|
tab_classes_html.append(
|
|
f"<PresentationTab id='{class_name}'>"
|
|
f"<Tabs{class_name} /></PresentationTab>"
|
|
)
|
|
workflows_js.append(
|
|
f'import {{ default as {class_name} }} '
|
|
f'from "./{class_name}/workflow.json" '
|
|
'with { type: "json" };'
|
|
)
|
|
workflow_class_names.append(
|
|
class_name
|
|
)
|
|
|
|
workflows_js_str = "\n".join(workflows_js)
|
|
tabs_html = "\n".join(tab_classes_html)
|
|
page = (
|
|
f"<!-- {WebBuilder.generated_message} -->"
|
|
f"\n<template>\n{tabs_html}\n</template>"
|
|
)
|
|
workflow_class_names_str = ",".join(workflow_class_names)
|
|
workflows_list_js = (
|
|
f"// {WebBuilder.generated_message}\n"
|
|
f"{workflows_js_str}\n"
|
|
f"export default {{ {workflow_class_names_str} }};\n"
|
|
)
|
|
|
|
if (
|
|
page != tab_list_orig
|
|
or os.path.exists(workflows_list_path)
|
|
or os.path.exists(tab_list_path)
|
|
):
|
|
self.write_file(tab_list_path, page)
|
|
self.write_file(workflows_list_path, workflows_list_js)
|
|
return True
|
|
|
|
return False
|
|
|
|
def fixFileName(self, name):
|
|
return re.sub(r"[^a-zA-Z0-9\-_]", '', name)
|
|
|
|
def isUserRequestedStop(self):
|
|
return os.path.exists(self.userRequestedStopFile)
|
|
|
|
def setUserRequestedStop(self, stop):
|
|
if stop:
|
|
os.close(os.open(self.userRequestedStopFile, os.O_CREAT))
|
|
else:
|
|
if self.isUserRequestedStop():
|
|
os.unlink(self.userRequestedStopFile)
|
|
|
|
async def web_stop(self, request):
|
|
try:
|
|
logger.info("web_stop")
|
|
pid = self.kill_web_process_running()
|
|
self.setUserRequestedStop(True)
|
|
if pid is None:
|
|
message = "pid already stopped"
|
|
else:
|
|
message = f"pid stopped: {pid}"
|
|
return web.json_response({
|
|
"ok": True,
|
|
"message": message,
|
|
})
|
|
except Exception as e:
|
|
logger.error(f"stop Error {e}")
|
|
return web.json_response({
|
|
"ok": False,
|
|
"message": str(e),
|
|
}, status=500)
|
|
|
|
async def web_restart(self, request):
|
|
try:
|
|
logger.info("web_restart")
|
|
self.setUserRequestedStop(False)
|
|
self.kill_web_process_running()
|
|
time.sleep(3)
|
|
self.start_web_server()
|
|
time.sleep(3)
|
|
pid = self.get_web_process_running()
|
|
return web.json_response({
|
|
"ok": True,
|
|
"message": f"PID: {pid}",
|
|
})
|
|
except Exception as e:
|
|
logger.error(f"restart Error {e}")
|
|
return web.json_response({
|
|
"ok": False,
|
|
"message": str(e),
|
|
}, status=500)
|
|
|
|
async def web_rebuild(self, request):
|
|
try:
|
|
self.kill_web_process_running()
|
|
time.sleep(1)
|
|
|
|
self.run_web_server("cleanup").wait()
|
|
self.run_web_server("build").wait()
|
|
self.start_web_server()
|
|
time.sleep(3)
|
|
pid = self.get_web_process_running()
|
|
|
|
return web.json_response({
|
|
"ok": True,
|
|
"message": f"PID: {pid}",
|
|
})
|
|
except Exception as e:
|
|
logger.error(f"rebuild Error {e}")
|
|
return web.json_response({
|
|
"ok": False,
|
|
"message": str(e),
|
|
}, status=500)
|
|
|
|
async def web_save(self, request):
|
|
try:
|
|
logger.info("web_save")
|
|
js = await request.json()
|
|
name = self.fixFileName(js['name'])
|
|
camelTabName = self.fixFileName(js['camelTabName'])
|
|
|
|
customs_path = os.path.join(
|
|
self.dest_path,
|
|
f"components/tabs/{camelTabName}"
|
|
)
|
|
if not os.path.exists(customs_path):
|
|
os.makedirs(customs_path)
|
|
customs = js['customs']
|
|
for custom_id, custom in customs.items():
|
|
custom_filename = f"{custom_id}.vue"
|
|
self.write_file(os.path.join(
|
|
customs_path,
|
|
custom_filename
|
|
), custom)
|
|
|
|
workflow = js['workflow']
|
|
template = js['template']
|
|
|
|
self.write_file(os.path.join(
|
|
customs_path,
|
|
"workflow.json"
|
|
), json.dumps(workflow))
|
|
|
|
self.write_file(os.path.join(
|
|
self.get_tabs_path(),
|
|
f"{camelTabName}.vue"
|
|
), template)
|
|
|
|
self.make_tab_list()
|
|
self.start_web_server()
|
|
return web.json_response({
|
|
"ok": True,
|
|
"name": name,
|
|
"camelTabName": camelTabName,
|
|
})
|
|
except Exception as e:
|
|
logger.error(f"save Error {e}")
|
|
return web.json_response({
|
|
"ok": False,
|
|
"message": str(e),
|
|
}, status=500)
|
|
|
|
def wait_fork(self, p):
|
|
logger.info("wait nuxt process")
|
|
WebBuilder.web_process.wait()
|
|
logger.info(f"nuxt process ended. pid:{WebBuilder.web_process.pid}")
|
|
WebBuilder.web_process = None
|
|
|
|
def exit_handler(self):
|
|
self.kill_web_process_running()
|
|
|
|
def run_web_server(self, run):
|
|
return subprocess.Popen(
|
|
['npm', 'run', run],
|
|
shell=True,
|
|
stdin=sys.stdin,
|
|
stdout=sys.stdout,
|
|
stderr=sys.stderr,
|
|
env=os.environ,
|
|
cwd=self.dest_path,
|
|
)
|
|
|
|
def start_web_server(self, run="dev"):
|
|
if self.isUserRequestedStop():
|
|
return None
|
|
|
|
if self.get_web_process_running() is not None:
|
|
logger.debug(
|
|
"nuxt server has already been started, " +
|
|
f"pid: {self.get_web_process_running()}"
|
|
)
|
|
return None
|
|
|
|
if not WebBuilder.exit_handler_done:
|
|
atexit.register(self.exit_handler)
|
|
WebBuilder.exit_handler_done = True
|
|
|
|
WebBuilder.web_process = self.run_web_server('dev')
|
|
logger.info(f"nuxt pid: {WebBuilder.web_process.pid}")
|
|
|
|
# Start thread to listen for process end
|
|
threading.Thread(
|
|
target=self.wait_fork,
|
|
args=(WebBuilder.web_process,),
|
|
daemon=True
|
|
).start()
|
|
|
|
def copy_nuxt(self):
|
|
if not os.path.exists(self.dest_path):
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
source_dir = os.path.join(script_dir, self.dest_path_short)
|
|
shutil.copytree(source_dir, self.dest_path)
|
|
|
|
def install_nuxt(self):
|
|
self.copy_nuxt()
|
|
if (
|
|
not WebBuilder.install_started and
|
|
not os.path.exists(os.path.join(self.dest_path, "node_modules"))
|
|
):
|
|
WebBuilder.install_started = True
|
|
logger.info("run npm install")
|
|
p = subprocess.Popen(
|
|
['npm', 'install'],
|
|
shell=True,
|
|
stdin=sys.stdin,
|
|
stdout=sys.stdout,
|
|
stderr=sys.stderr,
|
|
cwd=self.dest_path,
|
|
)
|
|
ret = p.wait()
|
|
if ret != 0:
|
|
logger.info(f"install nuxt failed: {ret}")
|
|
return False
|
|
|
|
logger.info("install_server end")
|
|
self.start_web_server()
|
|
logger.info("start_web_server done")
|
|
return True
|