diff --git a/__init__.py b/__init__.py
index adf4357..79e056d 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,106 +1,33 @@
-import configparser
import mimetypes
-import shutil
import traceback
import folder_paths
-import os
-import sys
-import threading
import locale
import subprocess # don't remove this
-from tqdm.auto import tqdm
import concurrent
-from urllib.parse import urlparse
-import http.client
-import re
import nodes
import hashlib
+import os
+import sys
+import threading
+import re
+import shutil
from datetime import datetime
-from .glob.manager_util import *
+# glob_path = os.path.join(os.path.dirname(__file__), "glob")
+# sys.path.append(glob_path)
-try:
- import cm_global
-except:
- glob_path = os.path.join(os.path.dirname(__file__), "glob")
- sys.path.append(glob_path)
- import cm_global
-
- print(f"[WARN] ComfyUI-Manager: Your ComfyUI version is outdated. Please update to the latest version.")
-
+from server import PromptServer
+from .glob import manager_core as core
+import cm_global
-version = [2, 17]
+version = [2, 18]
version_str = f"V{version[0]}.{version[1]}" + (f'.{version[2]}' if len(version) > 2 else '')
print(f"### Loading: ComfyUI-Manager ({version_str})")
comfy_ui_hash = "-"
-cache_lock = threading.Lock()
-
-pip_map = None
-
-
-def remap_pip_package(pkg):
- if pkg in cm_global.pip_overrides:
- res = cm_global.pip_overrides[pkg]
- print(f"[ComfyUI-Manager] '{pkg}' is remapped to '{res}'")
- return res
- else:
- return pkg
-
-
-def get_installed_packages():
- global pip_map
-
- if pip_map is None:
- try:
- result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True)
-
- pip_map = {}
- for line in result.split('\n'):
- x = line.strip()
- if x:
- y = line.split()
- if y[0] == 'Package' or y[0].startswith('-'):
- continue
-
- pip_map[y[0]] = y[1]
- except subprocess.CalledProcessError as e:
- print(f"[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.")
- return set()
-
- return pip_map
-
-
-def clear_pip_cache():
- global pip_map
- pip_map = None
-
-
-def is_blacklisted(name):
- name = name.strip()
-
- pattern = r'([^<>!=]+)([<>!=]=?)(.*)'
- match = re.search(pattern, name)
-
- if match:
- name = match.group(1)
-
- if name in cm_global.pip_downgrade_blacklist:
- pips = get_installed_packages()
-
- if match is None:
- if name in pips:
- return True
- elif match.group(2) in ['<=', '==', '<']:
- if name in pips:
- if StrictVersion(pips[name]) >= StrictVersion(match.group(3)):
- return True
-
- return False
-
def handle_stream(stream, prefix):
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
@@ -117,23 +44,41 @@ def handle_stream(stream, prefix):
print(prefix, msg, end="")
-def run_script(cmd, cwd='.'):
- if len(cmd) > 0 and cmd[0].startswith("#"):
- print(f"[ComfyUI-Manager] Unexpected behavior: `{cmd}`")
- return 0
+from comfy.cli_args import args
+import latent_preview
+
+
+class ManagerFuncsInComfyUI(core.ManagerFuncs):
+ def get_current_preview_method(self):
+ if args.preview_method == latent_preview.LatentPreviewMethod.Auto:
+ return "auto"
+ elif args.preview_method == latent_preview.LatentPreviewMethod.Latent2RGB:
+ return "latent2rgb"
+ elif args.preview_method == latent_preview.LatentPreviewMethod.TAESD:
+ return "taesd"
+ else:
+ return "none"
+
+ def run_script(self, cmd, cwd='.'):
+ if len(cmd) > 0 and cmd[0].startswith("#"):
+ print(f"[ComfyUI-Manager] Unexpected behavior: `{cmd}`")
+ return 0
- process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
+ process = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1)
- stdout_thread = threading.Thread(target=handle_stream, args=(process.stdout, ""))
- stderr_thread = threading.Thread(target=handle_stream, args=(process.stderr, "[!]"))
+ stdout_thread = threading.Thread(target=handle_stream, args=(process.stdout, ""))
+ stderr_thread = threading.Thread(target=handle_stream, args=(process.stderr, "[!]"))
- stdout_thread.start()
- stderr_thread.start()
+ stdout_thread.start()
+ stderr_thread.start()
- stdout_thread.join()
- stderr_thread.join()
+ stdout_thread.join()
+ stderr_thread.join()
- return process.wait()
+ return process.wait()
+
+
+core.manager_funcs = ManagerFuncsInComfyUI()
try:
@@ -144,13 +89,13 @@ except:
print(f"## ComfyUI-Manager: installing dependencies")
- run_script([sys.executable, '-s', '-m', 'pip', 'install', '-r', requirements_path])
+ core.manager_funcs.run_script([sys.executable, '-s', '-m', 'pip', 'install', '-r', requirements_path])
try:
import git
except:
print(f"## [ERROR] ComfyUI-Manager: Attempting to reinstall dependencies using an alternative method.")
- run_script([sys.executable, '-s', '-m', 'pip', 'install', '--user', '-r', requirements_path])
+ core.manager_funcs.run_script([sys.executable, '-s', '-m', 'pip', 'install', '--user', '-r', requirements_path])
try:
import git
@@ -160,152 +105,21 @@ except:
print(f"## ComfyUI-Manager: installing dependencies done.")
-from git.remote import RemoteProgress
+
sys.path.append('../..')
from torchvision.datasets.utils import download_url
-comfy_ui_required_revision = 1930
-comfy_ui_required_commit_datetime = datetime(2024, 1, 24, 0, 0, 0)
-
-comfy_ui_revision = "Unknown"
-comfy_ui_commit_datetime = datetime(1900, 1, 1, 0, 0, 0)
comfy_path = os.path.dirname(folder_paths.__file__)
-custom_nodes_path = os.path.join(comfy_path, 'custom_nodes')
-js_path = os.path.join(comfy_path, "web", "extensions")
-
-comfyui_manager_path = os.path.dirname(__file__)
-cache_dir = os.path.join(comfyui_manager_path, '.cache')
-local_db_model = os.path.join(comfyui_manager_path, "model-list.json")
-local_db_alter = os.path.join(comfyui_manager_path, "alter-list.json")
-local_db_custom_node_list = os.path.join(comfyui_manager_path, "custom-node-list.json")
-local_db_extension_node_mappings = os.path.join(comfyui_manager_path, "extension-node-map.json")
-git_script_path = os.path.join(os.path.dirname(__file__), "git_helper.py")
-components_path = os.path.join(comfyui_manager_path, 'components')
-
-startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts")
-config_path = os.path.join(os.path.dirname(__file__), "config.ini")
-cached_config = None
-
-channel_list_path = os.path.join(comfyui_manager_path, 'channels.list')
-channel_dict = None
-channel_list = None
-
-from comfy.cli_args import args
-import latent_preview
-
-
-def get_channel_dict():
- global channel_dict
-
- if channel_dict is None:
- channel_dict = {}
-
- if not os.path.exists(channel_list_path):
- shutil.copy(channel_list_path+'.template', channel_list_path)
-
- with open(os.path.join(comfyui_manager_path, 'channels.list'), 'r') as file:
- channels = file.read()
- for x in channels.split('\n'):
- channel_info = x.split("::")
- if len(channel_info) == 2:
- channel_dict[channel_info[0]] = channel_info[1]
-
- return channel_dict
-
-
-def get_channel_list():
- global channel_list
-
- if channel_list is None:
- channel_list = []
- for k, v in get_channel_dict().items():
- channel_list.append(f"{k}::{v}")
-
- return channel_list
-
-
-def write_config():
- config = configparser.ConfigParser()
- config['default'] = {
- 'preview_method': get_current_preview_method(),
- 'badge_mode': get_config()['badge_mode'],
- 'git_exe': get_config()['git_exe'],
- 'channel_url': get_config()['channel_url'],
- 'share_option': get_config()['share_option'],
- 'bypass_ssl': get_config()['bypass_ssl'],
- "file_logging": get_config()['file_logging'],
- 'default_ui': get_config()['default_ui'],
- 'component_policy': get_config()['component_policy'],
- 'double_click_policy': get_config()['double_click_policy'],
- 'windows_selector_event_loop_policy': get_config()['windows_selector_event_loop_policy'],
- 'model_download_by_agent': get_config()['model_download_by_agent'],
- 'downgrade_blacklist': get_config()['downgrade_blacklist']
- }
- with open(config_path, 'w') as configfile:
- config.write(configfile)
+core.js_path = os.path.join(comfy_path, "web", "extensions")
-
-def read_config():
- try:
- config = configparser.ConfigParser()
- config.read(config_path)
- default_conf = config['default']
-
- return {
- 'preview_method': default_conf['preview_method'] if 'preview_method' in default_conf else get_current_preview_method(),
- 'badge_mode': default_conf['badge_mode'] if 'badge_mode' in default_conf else 'none',
- 'git_exe': default_conf['git_exe'] if 'git_exe' in default_conf else '',
- 'channel_url': default_conf['channel_url'] if 'channel_url' in default_conf else 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main',
- 'share_option': default_conf['share_option'] if 'share_option' in default_conf else 'all',
- 'bypass_ssl': default_conf['bypass_ssl'] if 'bypass_ssl' in default_conf else False,
- 'file_logging': default_conf['file_logging'] if 'file_logging' in default_conf else True,
- 'default_ui': default_conf['default_ui'] if 'default_ui' in default_conf else 'none',
- 'component_policy': default_conf['component_policy'] if 'component_policy' in default_conf else 'workflow',
- 'double_click_policy': default_conf['double_click_policy'] if 'double_click_policy' in default_conf else 'copy-all',
- 'windows_selector_event_loop_policy': default_conf['windows_selector_event_loop_policy'] if 'windows_selector_event_loop_policy' in default_conf else False,
- 'model_download_by_agent': default_conf['model_download_by_agent'] if 'model_download_by_agent' in default_conf else False,
- 'downgrade_blacklist': default_conf['downgrade_blacklist'] if 'downgrade_blacklist' in default_conf else '',
- }
-
- except Exception:
- return {
- 'preview_method': get_current_preview_method(),
- 'badge_mode': 'none',
- 'git_exe': '',
- 'channel_url': 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main',
- 'share_option': 'all',
- 'bypass_ssl': False,
- 'file_logging': True,
- 'default_ui': 'none',
- 'component_policy': 'workflow',
- 'double_click_policy': 'copy-all',
- 'windows_selector_event_loop_policy': False,
- 'model_download_by_agent': False,
- 'downgrade_blacklist': ''
- }
-
-
-def get_config():
- global cached_config
-
- if cached_config is None:
- cached_config = read_config()
-
- return cached_config
-
-
-def get_current_preview_method():
- if args.preview_method == latent_preview.LatentPreviewMethod.Auto:
- return "auto"
- elif args.preview_method == latent_preview.LatentPreviewMethod.Latent2RGB:
- return "latent2rgb"
- elif args.preview_method == latent_preview.LatentPreviewMethod.TAESD:
- return "taesd"
- else:
- return "none"
+local_db_model = os.path.join(core.comfyui_manager_path, "model-list.json")
+local_db_alter = os.path.join(core.comfyui_manager_path, "alter-list.json")
+local_db_custom_node_list = os.path.join(core.comfyui_manager_path, "custom-node-list.json")
+local_db_extension_node_mappings = os.path.join(core.comfyui_manager_path, "extension-node-map.json")
+components_path = os.path.join(core.comfyui_manager_path, 'components')
def set_preview_method(method):
@@ -318,63 +132,27 @@ def set_preview_method(method):
else:
args.preview_method = latent_preview.LatentPreviewMethod.NoPreviews
- get_config()['preview_method'] = args.preview_method
+ core.get_config()['preview_method'] = args.preview_method
-set_preview_method(get_config()['preview_method'])
+set_preview_method(core.get_config()['preview_method'])
def set_badge_mode(mode):
- get_config()['badge_mode'] = mode
+ core.get_config()['badge_mode'] = mode
def set_default_ui_mode(mode):
- get_config()['default_ui'] = mode
+ core.get_config()['default_ui'] = mode
def set_component_policy(mode):
- get_config()['component_policy'] = mode
+ core.get_config()['component_policy'] = mode
def set_double_click_policy(mode):
- get_config()['double_click_policy'] = mode
-
-
-def try_install_script(url, repo_path, install_cmd):
- if (len(install_cmd) > 0 and install_cmd[0].startswith('#')) or (platform.system() == "Windows" and comfy_ui_commit_datetime.date() >= comfy_ui_required_commit_datetime.date()):
- if not os.path.exists(startup_script_path):
- os.makedirs(startup_script_path)
+ core.get_config()['double_click_policy'] = mode
- script_path = os.path.join(startup_script_path, "install-scripts.txt")
- with open(script_path, "a") as file:
- obj = [repo_path] + install_cmd
- file.write(f"{obj}\n")
-
- return True
- else:
- if len(install_cmd) == 5 and install_cmd[2:4] == ['pip', 'install']:
- if is_blacklisted(install_cmd[4]):
- print(f"[ComfyUI-Manager] skip black listed pip installation: '{install_cmd[4]}'")
- return True
-
- print(f"\n## ComfyUI-Manager: EXECUTE => {install_cmd}")
- code = run_script(install_cmd, cwd=repo_path)
-
- if platform.system() == "Windows":
- try:
- if comfy_ui_commit_datetime.date() < comfy_ui_required_commit_datetime.date():
- print("\n\n###################################################################")
- print(f"[WARN] ComfyUI-Manager: Your ComfyUI version ({comfy_ui_revision})[{comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version.")
- print(f"[WARN] The extension installation feature may not work properly in the current installed ComfyUI version on Windows environment.")
- print("###################################################################\n\n")
- except:
- pass
-
- if code != 0:
- if url is None:
- url = os.path.dirname(repo_path)
- print(f"install script failed: {url}")
- return False
def print_comfyui_version():
global comfy_ui_revision
@@ -396,7 +174,7 @@ def print_comfyui_version():
current_branch = repo.active_branch.name
try:
- if comfy_ui_commit_datetime.date() < comfy_ui_required_commit_datetime.date():
+ if comfy_ui_commit_datetime.date() < core.comfy_ui_required_commit_datetime.date():
print(f"\n\n## [WARN] ComfyUI-Manager: Your ComfyUI version ({comfy_ui_revision})[{comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version. ##\n\n")
except:
pass
@@ -429,209 +207,8 @@ def print_comfyui_version():
print_comfyui_version()
-# use subprocess to avoid file system lock by git (Windows)
-def __win_check_git_update(path, do_fetch=False, do_update=False):
- if do_fetch:
- command = [sys.executable, git_script_path, "--fetch", path]
- elif do_update:
- command = [sys.executable, git_script_path, "--pull", path]
- else:
- command = [sys.executable, git_script_path, "--check", path]
-
- process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- output, _ = process.communicate()
- output = output.decode('utf-8').strip()
-
- if 'detected dubious' in output:
- # fix and try again
- safedir_path = path.replace('\\', '/')
- try:
- print(f"[ComfyUI-Manager] Try fixing 'dubious repository' error on '{safedir_path}' repo")
- process = subprocess.Popen(['git', 'config', '--global', '--add', 'safe.directory', safedir_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- output, _ = process.communicate()
-
- process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- output, _ = process.communicate()
- output = output.decode('utf-8').strip()
- except Exception:
- print(f'[ComfyUI-Manager] failed to fixing')
-
- if 'detected dubious' in output:
- print(f'\n[ComfyUI-Manager] Failed to fixing repository setup. Please execute this command on cmd: \n'
- f'-----------------------------------------------------------------------------------------\n'
- f'git config --global --add safe.directory "{safedir_path}"\n'
- f'-----------------------------------------------------------------------------------------\n')
-
- if do_update:
- if "CUSTOM NODE PULL: Success" in output:
- process.wait()
- print(f"\rUpdated: {path}")
- return True, True # updated
- elif "CUSTOM NODE PULL: None" in output:
- process.wait()
- return False, True # there is no update
- else:
- print(f"\rUpdate error: {path}")
- process.wait()
- return False, False # update failed
- else:
- if "CUSTOM NODE CHECK: True" in output:
- process.wait()
- return True, True
- elif "CUSTOM NODE CHECK: False" in output:
- process.wait()
- return False, True
- else:
- print(f"\rFetch error: {path}")
- print(f"\n{output}\n")
- process.wait()
- return False, True
-
-
-def __win_check_git_pull(path):
- command = [sys.executable, git_script_path, "--pull", path]
- process = subprocess.Popen(command)
- process.wait()
-
-
-def switch_to_default_branch(repo):
- show_result = repo.git.remote("show", "origin")
- matches = re.search(r"\s*HEAD branch:\s*(.*)", show_result)
- if matches:
- default_branch = matches.group(1)
- repo.git.checkout(default_branch)
-
-
-def git_repo_has_updates(path, do_fetch=False, do_update=False):
- if do_fetch:
- print(f"\x1b[2K\rFetching: {path}", end='')
- elif do_update:
- print(f"\x1b[2K\rUpdating: {path}", end='')
-
- # Check if the path is a git repository
- if not os.path.exists(os.path.join(path, '.git')):
- raise ValueError('Not a git repository')
-
- if platform.system() == "Windows":
- updated, success = __win_check_git_update(path, do_fetch, do_update)
- if updated and success:
- execute_install_script(None, path, lazy_mode=True)
- return updated, success
- else:
- # Fetch the latest commits from the remote repository
- repo = git.Repo(path)
-
- current_branch = repo.active_branch
- branch_name = current_branch.name
-
- remote_name = 'origin'
- remote = repo.remote(name=remote_name)
-
- # Get the current commit hash
- commit_hash = repo.head.commit.hexsha
-
- if do_fetch or do_update:
- remote.fetch()
-
- if do_update:
- if repo.head.is_detached:
- switch_to_default_branch(repo)
-
- remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
-
- if commit_hash == remote_commit_hash:
- repo.close()
- return False, True
-
- try:
- remote.pull()
- repo.git.submodule('update', '--init', '--recursive')
- new_commit_hash = repo.head.commit.hexsha
-
- if commit_hash != new_commit_hash:
- execute_install_script(None, path)
- print(f"\x1b[2K\rUpdated: {path}")
- return True, True
- else:
- return False, False
-
- except Exception as e:
- print(f"\nUpdating failed: {path}\n{e}", file=sys.stderr)
- return False, False
-
- if repo.head.is_detached:
- repo.close()
- return True, True
-
- # Get commit hash of the remote branch
- current_branch = repo.active_branch
- branch_name = current_branch.name
-
- remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
-
- # Compare the commit hashes to determine if the local repository is behind the remote repository
- if commit_hash != remote_commit_hash:
- # Get the commit dates
- commit_date = repo.head.commit.committed_datetime
- remote_commit_date = repo.refs[f'{remote_name}/{branch_name}'].object.committed_datetime
-
- # Compare the commit dates to determine if the local repository is behind the remote repository
- if commit_date < remote_commit_date:
- repo.close()
- return True, True
-
- repo.close()
-
- return False, True
-
-
-def git_pull(path):
- # Check if the path is a git repository
- if not os.path.exists(os.path.join(path, '.git')):
- raise ValueError('Not a git repository')
-
- # Pull the latest changes from the remote repository
- if platform.system() == "Windows":
- return __win_check_git_pull(path)
- else:
- repo = git.Repo(path)
-
- if repo.is_dirty():
- repo.git.stash()
-
- if repo.head.is_detached:
- switch_to_default_branch(repo)
-
- current_branch = repo.active_branch
- remote_name = current_branch.tracking_branch().remote_name
- remote = repo.remote(name=remote_name)
-
- remote.pull()
- repo.git.submodule('update', '--init', '--recursive')
-
- repo.close()
-
- return True
-
-
-async def get_data(uri, silent=False):
- if not silent:
- print(f"FETCH DATA from: {uri}")
-
- if uri.startswith("http"):
- async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
- async with session.get(uri) as resp:
- json_text = await resp.text()
- else:
- with cache_lock:
- with open(uri, "r", encoding="utf-8") as f:
- json_text = f.read()
-
- json_obj = json.loads(json_text)
- return json_obj
-
async def populate_github_stats(json_obj, filename, silent=False):
- uri = os.path.join(comfyui_manager_path, filename)
+ uri = os.path.join(core.comfyui_manager_path, filename)
with open(uri, "r", encoding='utf-8') as f:
github_stats = json.load(f)
if 'custom_nodes' in json_obj:
@@ -645,29 +222,9 @@ async def populate_github_stats(json_obj, filename, silent=False):
json_obj['custom_nodes'][i]['last_update'] = -1
return json_obj
-def setup_js():
- import nodes
- js_dest_path = os.path.join(js_path, "comfyui-manager")
-
- if hasattr(nodes, "EXTENSION_WEB_DIRS"):
- if os.path.exists(js_dest_path):
- shutil.rmtree(js_dest_path)
- else:
- print(f"[WARN] ComfyUI-Manager: Your ComfyUI version is outdated. Please update to the latest version.")
- # setup js
- if not os.path.exists(js_dest_path):
- os.makedirs(js_dest_path)
- js_src_path = os.path.join(comfyui_manager_path, "js", "comfyui-manager.js")
-
- print(f"### ComfyUI-Manager: Copy .js from '{js_src_path}' to '{js_dest_path}'")
- shutil.copy(js_src_path, js_dest_path)
-
-
-setup_js()
-
def setup_environment():
- git_exe = get_config()['git_exe']
+ git_exe = core.get_config()['git_exe']
if git_exe != '':
git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=git_exe)
@@ -686,57 +243,6 @@ import zipfile
import urllib.request
-def simple_hash(input_string):
- hash_value = 0
- for char in input_string:
- hash_value = (hash_value * 31 + ord(char)) % (2**32)
-
- return hash_value
-
-
-def is_file_created_within_one_day(file_path):
- if not os.path.exists(file_path):
- return False
-
- file_creation_time = os.path.getctime(file_path)
- current_time = datetime.now().timestamp()
- time_difference = current_time - file_creation_time
-
- return time_difference <= 86400
-
-
-async def get_data_by_mode(mode, filename):
- try:
- if mode == "local":
- uri = os.path.join(comfyui_manager_path, filename)
- json_obj = await get_data(uri)
- else:
- uri = get_config()['channel_url'] + '/' + filename
- cache_uri = str(simple_hash(uri))+'_'+filename
- cache_uri = os.path.join(cache_dir, cache_uri)
-
- if mode == "cache":
- if is_file_created_within_one_day(cache_uri):
- json_obj = await get_data(cache_uri)
- else:
- json_obj = await get_data(uri)
- with cache_lock:
- with open(cache_uri, "w", encoding='utf-8') as file:
- json.dump(json_obj, file, indent=4, sort_keys=True)
- else:
- uri = get_config()['channel_url'] + '/' + filename
- json_obj = await get_data(uri)
- with cache_lock:
- with open(cache_uri, "w", encoding='utf-8') as file:
- json.dump(json_obj, file, indent=4, sort_keys=True)
- except Exception as e:
- print(f"[ComfyUI-Manager] Due to a network error, switching to local mode.\n=> {filename}\n=> {e}")
- uri = os.path.join(comfyui_manager_path, filename)
- json_obj = await get_data(uri)
-
- return json_obj
-
-
def get_model_dir(data):
if data['save_path'] != 'default':
if '..' in data['save_path'] or data['save_path'].startswith('/'):
@@ -782,63 +288,6 @@ def get_model_path(data):
return os.path.join(base_model, data['filename'])
-def check_a_custom_node_installed(item, do_fetch=False, do_update_check=True, do_update=False):
- item['installed'] = 'None'
-
- if item['install_type'] == 'git-clone' and len(item['files']) == 1:
- url = item['files'][0]
-
- if url.endswith("/"):
- url = url[:-1]
-
- dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "")
- dir_path = os.path.join(custom_nodes_path, dir_name)
- if os.path.exists(dir_path):
- try:
- item['installed'] = 'True' # default
-
- if cm_global.try_call(api="cm.is_import_failed_extension", name=dir_name):
- item['installed'] = 'Fail'
-
- if do_update_check:
- update_state, success = git_repo_has_updates(dir_path, do_fetch, do_update)
- if (do_update_check or do_update) and update_state:
- item['installed'] = 'Update'
- elif do_update and not success:
- item['installed'] = 'Fail'
- except:
- if cm_global.try_call(api="cm.is_import_failed_extension", name=dir_name):
- item['installed'] = 'Fail'
- else:
- item['installed'] = 'True'
-
- elif os.path.exists(dir_path + ".disabled"):
- item['installed'] = 'Disabled'
-
- else:
- item['installed'] = 'False'
-
- elif item['install_type'] == 'copy' and len(item['files']) == 1:
- dir_name = os.path.basename(item['files'][0])
-
- if item['files'][0].endswith('.py'):
- base_path = custom_nodes_path
- elif 'js_path' in item:
- base_path = os.path.join(js_path, item['js_path'])
- else:
- base_path = js_path
-
- file_path = os.path.join(base_path, dir_name)
- if os.path.exists(file_path):
- if cm_global.try_call(api="cm.is_import_failed_extension", name=dir_name):
- item['installed'] = 'Fail'
- else:
- item['installed'] = 'True'
- elif os.path.exists(file_path + ".disabled"):
- item['installed'] = 'Disabled'
- else:
- item['installed'] = 'False'
-
def check_custom_nodes_installed(json_obj, do_fetch=False, do_update_check=True, do_update=False):
if do_fetch:
@@ -849,7 +298,7 @@ def check_custom_nodes_installed(json_obj, do_fetch=False, do_update_check=True,
print("Start update check...", end="")
def process_custom_node(item):
- check_a_custom_node_installed(item, do_fetch, do_update_check, do_update)
+ core.check_a_custom_node_installed(item, do_fetch, do_update_check, do_update)
with concurrent.futures.ThreadPoolExecutor(4) as executor:
for item in json_obj['custom_nodes']:
@@ -895,7 +344,7 @@ def nickname_filter(json_obj):
return json_obj
-@server.PromptServer.instance.routes.get("/customnode/getmappings")
+@PromptServer.instance.routes.get("/customnode/getmappings")
async def fetch_customnode_mappings(request):
mode = request.rel_url.query["mode"]
@@ -904,7 +353,7 @@ async def fetch_customnode_mappings(request):
mode = "local"
nickname_mode = True
- json_obj = await get_data_by_mode(mode, 'extension-node-map.json')
+ json_obj = await core.get_data_by_mode(mode, 'extension-node-map.json')
if nickname_mode:
json_obj = nickname_filter(json_obj)
@@ -927,10 +376,10 @@ async def fetch_customnode_mappings(request):
return web.json_response(json_obj, content_type='application/json')
-@server.PromptServer.instance.routes.get("/customnode/fetch_updates")
+@PromptServer.instance.routes.get("/customnode/fetch_updates")
async def fetch_updates(request):
try:
- json_obj = await get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
+ json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
check_custom_nodes_installed(json_obj, True)
@@ -945,12 +394,12 @@ async def fetch_updates(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.get("/customnode/update_all")
+@PromptServer.instance.routes.get("/customnode/update_all")
async def update_all(request):
try:
save_snapshot_with_postfix('autosave')
- json_obj = await get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
+ json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
check_custom_nodes_installed(json_obj, do_update=True)
@@ -968,7 +417,7 @@ async def update_all(request):
except:
return web.Response(status=400)
finally:
- clear_pip_cache()
+ core.clear_pip_cache()
def convert_markdown_to_html(input_text):
@@ -1015,7 +464,7 @@ def populate_markdown(x):
x['title'] = x['title'].replace('<', '<').replace('>', '>')
-@server.PromptServer.instance.routes.get("/customnode/getlist")
+@PromptServer.instance.routes.get("/customnode/getlist")
async def fetch_customnode_list(request):
if "skip_update" in request.rel_url.query and request.rel_url.query["skip_update"] == "true":
skip_update = True
@@ -1025,9 +474,9 @@ async def fetch_customnode_list(request):
if request.rel_url.query["mode"] == "local":
channel = 'local'
else:
- channel = get_config()['channel_url']
+ channel = core.get_config()['channel_url']
- json_obj = await get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
+ json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
json_obj = await populate_github_stats(json_obj, "github-stats.json")
def is_ignored_notice(code):
@@ -1042,7 +491,6 @@ async def fetch_customnode_list(request):
else:
return False
-
json_obj['custom_nodes'] = [record for record in json_obj['custom_nodes'] if not is_ignored_notice(record.get('author'))]
check_custom_nodes_installed(json_obj, False, not skip_update)
@@ -1053,7 +501,7 @@ async def fetch_customnode_list(request):
if channel != 'local':
found = 'custom'
- for name, url in get_channel_dict().items():
+ for name, url in core.get_channel_dict().items():
if url == channel:
found = name
break
@@ -1065,15 +513,15 @@ async def fetch_customnode_list(request):
return web.json_response(json_obj, content_type='application/json')
-@server.PromptServer.instance.routes.get("/alternatives/getlist")
+@PromptServer.instance.routes.get("/alternatives/getlist")
async def fetch_alternatives_list(request):
if "skip_update" in request.rel_url.query and request.rel_url.query["skip_update"] == "true":
skip_update = True
else:
skip_update = False
- alter_json = await get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json')
- custom_node_json = await get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
+ alter_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'alter-list.json')
+ custom_node_json = await core.get_data_by_mode(request.rel_url.query["mode"], 'custom-node-list.json')
fileurl_to_custom_node = {}
@@ -1085,7 +533,7 @@ async def fetch_alternatives_list(request):
fileurl = item['id']
if fileurl in fileurl_to_custom_node:
custom_node = fileurl_to_custom_node[fileurl]
- check_a_custom_node_installed(custom_node, not skip_update)
+ core.check_a_custom_node_installed(custom_node, not skip_update)
populate_markdown(item)
populate_markdown(custom_node)
@@ -1110,9 +558,9 @@ def check_model_installed(json_obj):
executor.submit(process_model, item)
-@server.PromptServer.instance.routes.get("/externalmodel/getlist")
+@PromptServer.instance.routes.get("/externalmodel/getlist")
async def fetch_externalmodel_list(request):
- json_obj = await get_data_by_mode(request.rel_url.query["mode"], 'model-list.json')
+ json_obj = await core.get_data_by_mode(request.rel_url.query["mode"], 'model-list.json')
check_model_installed(json_obj)
@@ -1122,7 +570,7 @@ async def fetch_externalmodel_list(request):
return web.json_response(json_obj, content_type='application/json')
-@server.PromptServer.instance.routes.get("/snapshot/getlist")
+@PromptServer.instance.routes.get("/snapshot/getlist")
async def get_snapshot_list(request):
snapshots_directory = os.path.join(os.path.dirname(__file__), 'snapshots')
items = [f[:-5] for f in os.listdir(snapshots_directory) if f.endswith('.json')]
@@ -1130,7 +578,7 @@ async def get_snapshot_list(request):
return web.json_response({'items': items}, content_type='application/json')
-@server.PromptServer.instance.routes.get("/snapshot/remove")
+@PromptServer.instance.routes.get("/snapshot/remove")
async def remove_snapshot(request):
try:
target = request.rel_url.query["target"]
@@ -1144,17 +592,17 @@ async def remove_snapshot(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.get("/snapshot/restore")
+@PromptServer.instance.routes.get("/snapshot/restore")
async def remove_snapshot(request):
try:
target = request.rel_url.query["target"]
path = os.path.join(os.path.dirname(__file__), 'snapshots', f"{target}.json")
if os.path.exists(path):
- if not os.path.exists(startup_script_path):
- os.makedirs(startup_script_path)
+ if not os.path.exists(core.startup_script_path):
+ os.makedirs(core.startup_script_path)
- target_path = os.path.join(startup_script_path, "restore-snapshot.json")
+ target_path = os.path.join(core.startup_script_path, "restore-snapshot.json")
shutil.copy(path, target_path)
print(f"Snapshot restore scheduled: `{target}`")
@@ -1181,8 +629,8 @@ def get_current_snapshot():
file_custom_nodes = []
# Get custom nodes hash
- for path in os.listdir(custom_nodes_path):
- fullpath = os.path.join(custom_nodes_path, path)
+ for path in os.listdir(core.custom_nodes_path):
+ fullpath = os.path.join(core.custom_nodes_path, path)
if os.path.isdir(fullpath):
is_disabled = path.endswith(".disabled")
@@ -1232,7 +680,7 @@ def save_snapshot_with_postfix(postfix):
json.dump(get_current_snapshot(), json_file, indent=4)
-@server.PromptServer.instance.routes.get("/snapshot/get_current")
+@PromptServer.instance.routes.get("/snapshot/get_current")
async def get_current_snapshot_api(request):
try:
return web.json_response(get_current_snapshot(), content_type='application/json')
@@ -1240,7 +688,7 @@ async def get_current_snapshot_api(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.get("/snapshot/save")
+@PromptServer.instance.routes.get("/snapshot/save")
async def save_snapshot(request):
try:
save_snapshot_with_postfix('snapshot')
@@ -1266,7 +714,7 @@ def unzip_install(files):
f.write(data)
with zipfile.ZipFile(temp_filename, 'r') as zip_ref:
- zip_ref.extractall(custom_nodes_path)
+ zip_ref.extractall(core.custom_nodes_path)
os.remove(temp_filename)
except Exception as e:
@@ -1306,9 +754,9 @@ def copy_install(files, js_path_name=None):
url = url[:-1]
try:
if url.endswith(".py"):
- download_url(url, custom_nodes_path)
+ download_url(url, core.custom_nodes_path)
else:
- path = os.path.join(js_path, js_path_name) if js_path_name is not None else js_path
+ path = os.path.join(core.js_path, js_path_name) if js_path_name is not None else core.js_path
if not os.path.exists(path):
os.makedirs(path)
download_url(url, path)
@@ -1326,7 +774,7 @@ def copy_uninstall(files, js_path_name='.'):
if url.endswith("/"):
url = url[:-1]
dir_name = os.path.basename(url)
- base_path = custom_nodes_path if url.endswith('.py') else os.path.join(js_path, js_path_name)
+ base_path = core.custom_nodes_path if url.endswith('.py') else os.path.join(core.js_path, js_path_name)
file_path = os.path.join(base_path, dir_name)
try:
@@ -1352,7 +800,7 @@ def copy_set_active(files, is_disable, js_path_name='.'):
if url.endswith("/"):
url = url[:-1]
dir_name = os.path.basename(url)
- base_path = custom_nodes_path if url.endswith('.py') else os.path.join(js_path, js_path_name)
+ base_path = core.custom_nodes_path if url.endswith('.py') else os.path.join(core.js_path, js_path_name)
file_path = os.path.join(base_path, dir_name)
try:
@@ -1374,258 +822,7 @@ def copy_set_active(files, is_disable, js_path_name='.'):
return True
-def execute_install_script(url, repo_path, lazy_mode=False):
- install_script_path = os.path.join(repo_path, "install.py")
- requirements_path = os.path.join(repo_path, "requirements.txt")
-
- if lazy_mode:
- install_cmd = ["#LAZY-INSTALL-SCRIPT", sys.executable]
- try_install_script(url, repo_path, install_cmd)
- else:
- if os.path.exists(requirements_path):
- print("Install: pip packages")
- with open(requirements_path, "r") as requirements_file:
- for line in requirements_file:
- package_name = remap_pip_package(line.strip())
- if package_name:
- install_cmd = [sys.executable, "-m", "pip", "install", package_name]
- if package_name.strip() != "":
- try_install_script(url, repo_path, install_cmd)
-
- if os.path.exists(install_script_path):
- print(f"Install: install script")
- install_cmd = [sys.executable, "install.py"]
- try_install_script(url, repo_path, install_cmd)
-
- return True
-
-
-class GitProgress(RemoteProgress):
- def __init__(self):
- super().__init__()
- self.pbar = tqdm()
-
- def update(self, op_code, cur_count, max_count=None, message=''):
- self.pbar.total = max_count
- self.pbar.n = cur_count
- self.pbar.pos = 0
- self.pbar.refresh()
-
-
-def is_valid_url(url):
- try:
- result = urlparse(url)
- return all([result.scheme, result.netloc])
- except ValueError:
- return False
-
-
-def gitclone_install(files):
- print(f"install: {files}")
- for url in files:
- if not is_valid_url(url):
- print(f"Invalid git url: '{url}'")
- return False
-
- if url.endswith("/"):
- url = url[:-1]
- try:
- print(f"Download: git clone '{url}'")
- repo_name = os.path.splitext(os.path.basename(url))[0]
- repo_path = os.path.join(custom_nodes_path, repo_name)
-
- # Clone the repository from the remote URL
- if platform.system() == 'Windows':
- res = run_script([sys.executable, git_script_path, "--clone", custom_nodes_path, url])
- if res != 0:
- return False
- else:
- repo = git.Repo.clone_from(url, repo_path, recursive=True, progress=GitProgress())
- repo.git.clear_cache()
- repo.close()
-
- if not execute_install_script(url, repo_path):
- return False
-
- except Exception as e:
- print(f"Install(git-clone) error: {url} / {e}", file=sys.stderr)
- return False
-
- print("Installation was successful.")
- return True
-
-
-def gitclone_fix(files):
- print(f"Try fixing: {files}")
- for url in files:
- if not is_valid_url(url):
- print(f"Invalid git url: '{url}'")
- return False
-
- if url.endswith("/"):
- url = url[:-1]
- try:
- repo_name = os.path.splitext(os.path.basename(url))[0]
- repo_path = os.path.join(custom_nodes_path, repo_name)
-
- if not execute_install_script(url, repo_path):
- return False
-
- except Exception as e:
- print(f"Install(git-clone) error: {url} / {e}", file=sys.stderr)
- return False
-
- print(f"Attempt to fixing '{files}' is done.")
- return True
-
-
-def pip_install(packages):
- install_cmd = ['#FORCE', sys.executable, "-m", "pip", "install", '-U'] + packages
- try_install_script('pip install via manager', '.', install_cmd)
-
-
-import platform
-import subprocess
-import time
-
-
-def rmtree(path):
- retry_count = 3
-
- while True:
- try:
- retry_count -= 1
-
- if platform.system() == "Windows":
- run_script(['attrib', '-R', path + '\\*', '/S'])
- shutil.rmtree(path)
-
- return True
-
- except Exception as ex:
- print(f"ex: {ex}")
- time.sleep(3)
-
- if retry_count < 0:
- raise ex
-
- print(f"Uninstall retry({retry_count})")
-
-
-def gitclone_uninstall(files):
- import shutil
- import os
-
- print(f"uninstall: {files}")
- for url in files:
- if url.endswith("/"):
- url = url[:-1]
- try:
- dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "")
- dir_path = os.path.join(custom_nodes_path, dir_name)
-
- # safety check
- if dir_path == '/' or dir_path[1:] == ":/" or dir_path == '':
- print(f"Uninstall(git-clone) error: invalid path '{dir_path}' for '{url}'")
- return False
-
- install_script_path = os.path.join(dir_path, "uninstall.py")
- disable_script_path = os.path.join(dir_path, "disable.py")
- if os.path.exists(install_script_path):
- uninstall_cmd = [sys.executable, "uninstall.py"]
- code = run_script(uninstall_cmd, cwd=dir_path)
-
- if code != 0:
- print(f"An error occurred during the execution of the uninstall.py script. Only the '{dir_path}' will be deleted.")
- elif os.path.exists(disable_script_path):
- disable_script = [sys.executable, "disable.py"]
- code = run_script(disable_script, cwd=dir_path)
- if code != 0:
- print(f"An error occurred during the execution of the disable.py script. Only the '{dir_path}' will be deleted.")
-
- if os.path.exists(dir_path):
- rmtree(dir_path)
- elif os.path.exists(dir_path + ".disabled"):
- rmtree(dir_path + ".disabled")
- except Exception as e:
- print(f"Uninstall(git-clone) error: {url} / {e}", file=sys.stderr)
- return False
-
- print("Uninstallation was successful.")
- return True
-
-
-def gitclone_set_active(files, is_disable):
- import os
-
- if is_disable:
- action_name = "Disable"
- else:
- action_name = "Enable"
-
- print(f"{action_name}: {files}")
- for url in files:
- if url.endswith("/"):
- url = url[:-1]
- try:
- dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "")
- dir_path = os.path.join(custom_nodes_path, dir_name)
-
- # safey check
- if dir_path == '/' or dir_path[1:] == ":/" or dir_path == '':
- print(f"{action_name}(git-clone) error: invalid path '{dir_path}' for '{url}'")
- return False
-
- if is_disable:
- current_path = dir_path
- new_path = dir_path + ".disabled"
- else:
- current_path = dir_path + ".disabled"
- new_path = dir_path
-
- os.rename(current_path, new_path)
-
- if is_disable:
- if os.path.exists(os.path.join(new_path, "disable.py")):
- disable_script = [sys.executable, "disable.py"]
- try_install_script(url, new_path, disable_script)
- else:
- if os.path.exists(os.path.join(new_path, "enable.py")):
- enable_script = [sys.executable, "enable.py"]
- try_install_script(url, new_path, enable_script)
-
- except Exception as e:
- print(f"{action_name}(git-clone) error: {url} / {e}", file=sys.stderr)
- return False
-
- print(f"{action_name} was successful.")
- return True
-
-
-def gitclone_update(files):
- import os
-
- print(f"Update: {files}")
- for url in files:
- if url.endswith("/"):
- url = url[:-1]
- try:
- repo_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "")
- repo_path = os.path.join(custom_nodes_path, repo_name)
- git_pull(repo_path)
-
- if not execute_install_script(url, repo_path, lazy_mode=True):
- return False
-
- except Exception as e:
- print(f"Update(git-clone) error: {url} / {e}", file=sys.stderr)
- return False
-
- print("Update was successful.")
- return True
-
-
-@server.PromptServer.instance.routes.post("/customnode/install")
+@PromptServer.instance.routes.post("/customnode/install")
async def install_custom_node(request):
json_data = await request.json()
@@ -1646,15 +843,15 @@ async def install_custom_node(request):
res = copy_install(json_data['files'], js_path_name)
elif install_type == "git-clone":
- res = gitclone_install(json_data['files'])
+ res = core.gitclone_install(json_data['files'])
if 'pip' in json_data:
for pname in json_data['pip']:
- pkg = remap_pip_package(pname)
+ pkg = core.remap_pip_package(pname)
install_cmd = [sys.executable, "-m", "pip", "install", pkg]
- try_install_script(json_data['files'][0], ".", install_cmd)
+ core.try_install_script(json_data['files'][0], ".", install_cmd)
- clear_pip_cache()
+ core.clear_pip_cache()
if res:
print(f"After restarting ComfyUI, please refresh the browser.")
@@ -1663,7 +860,7 @@ async def install_custom_node(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.post("/customnode/fix")
+@PromptServer.instance.routes.post("/customnode/fix")
async def fix_custom_node(request):
json_data = await request.json()
@@ -1677,14 +874,14 @@ async def fix_custom_node(request):
return web.Response(status=400)
if install_type == "git-clone":
- res = gitclone_fix(json_data['files'])
+ res = core.gitclone_fix(json_data['files'])
else:
return web.Response(status=400)
if 'pip' in json_data:
for pname in json_data['pip']:
install_cmd = [sys.executable, "-m", "pip", "install", '-U', pname]
- try_install_script(json_data['files'][0], ".", install_cmd)
+ core.try_install_script(json_data['files'][0], ".", install_cmd)
if res:
print(f"After restarting ComfyUI, please refresh the browser.")
@@ -1693,12 +890,12 @@ async def fix_custom_node(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.get("/customnode/install/git_url")
+@PromptServer.instance.routes.get("/customnode/install/git_url")
async def install_custom_node_git_url(request):
res = False
if "url" in request.rel_url.query:
url = request.rel_url.query['url']
- res = gitclone_install([url])
+ res = core.gitclone_install([url])
if res:
print(f"After restarting ComfyUI, please refresh the browser.")
@@ -1707,17 +904,17 @@ async def install_custom_node_git_url(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.get("/customnode/install/pip")
+@PromptServer.instance.routes.get("/customnode/install/pip")
async def install_custom_node_git_url(request):
res = False
if "packages" in request.rel_url.query:
packages = request.rel_url.query['packages']
- pip_install(packages.split(' '))
+ core.pip_install(packages.split(' '))
return web.Response(status=200)
-@server.PromptServer.instance.routes.post("/customnode/uninstall")
+@PromptServer.instance.routes.post("/customnode/uninstall")
async def uninstall_custom_node(request):
json_data = await request.json()
@@ -1732,7 +929,7 @@ async def uninstall_custom_node(request):
res = copy_uninstall(json_data['files'], js_path_name)
elif install_type == "git-clone":
- res = gitclone_uninstall(json_data['files'])
+ res = core.gitclone_uninstall(json_data['files'])
if res:
print(f"After restarting ComfyUI, please refresh the browser.")
@@ -1741,7 +938,7 @@ async def uninstall_custom_node(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.post("/customnode/update")
+@PromptServer.instance.routes.post("/customnode/update")
async def update_custom_node(request):
json_data = await request.json()
@@ -1752,9 +949,9 @@ async def update_custom_node(request):
res = False
if install_type == "git-clone":
- res = gitclone_update(json_data['files'])
+ res = core.gitclone_update(json_data['files'])
- clear_pip_cache()
+ core.clear_pip_cache()
if res:
print(f"After restarting ComfyUI, please refresh the browser.")
@@ -1763,7 +960,7 @@ async def update_custom_node(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.get("/comfyui_manager/update_comfyui")
+@PromptServer.instance.routes.get("/comfyui_manager/update_comfyui")
async def update_comfyui(request):
print(f"Update ComfyUI")
@@ -1778,7 +975,7 @@ async def update_comfyui(request):
repo = git.Repo(repo_path)
if repo.head.is_detached:
- switch_to_default_branch(repo)
+ core.switch_to_default_branch(repo)
current_branch = repo.active_branch
branch_name = current_branch.name
@@ -1809,8 +1006,8 @@ async def update_comfyui(request):
remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
if commit_hash != remote_commit_hash:
- git_pull(repo_path)
- execute_install_script("ComfyUI", repo_path)
+ core.git_pull(repo_path)
+ core.execute_install_script("ComfyUI", repo_path)
return web.Response(status=201)
else:
return web.Response(status=200)
@@ -1821,7 +1018,7 @@ async def update_comfyui(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.post("/customnode/toggle_active")
+@PromptServer.instance.routes.post("/customnode/toggle_active")
async def toggle_active(request):
json_data = await request.json()
@@ -1833,7 +1030,7 @@ async def toggle_active(request):
res = False
if install_type == "git-clone":
- res = gitclone_set_active(json_data['files'], not is_disabled)
+ res = core.gitclone_set_active(json_data['files'], not is_disabled)
elif install_type == "copy":
res = copy_set_active(json_data['files'], not is_disabled, json_data.get('js_path', None))
@@ -1843,7 +1040,7 @@ async def toggle_active(request):
return web.Response(status=400)
-@server.PromptServer.instance.routes.post("/model/install")
+@PromptServer.instance.routes.post("/model/install")
async def install_model(request):
json_data = await request.json()
@@ -1856,7 +1053,7 @@ async def install_model(request):
print(f"Install model '{json_data['name']}' into '{model_path}'")
model_url = json_data['url']
- if not get_config()['model_download_by_agent'] and (model_url.startswith('https://github.com') or model_url.startswith('https://huggingface.co') or model_url.startswith('https://heibox.uni-heidelberg.de')):
+ if not core.get_config()['model_download_by_agent'] and (model_url.startswith('https://github.com') or model_url.startswith('https://huggingface.co') or model_url.startswith('https://heibox.uni-heidelberg.de')):
model_dir = get_model_dir(json_data)
download_url(model_url, model_dir, filename=json_data['filename'])
@@ -1877,16 +1074,16 @@ async def install_model(request):
class ManagerTerminalHook:
def write_stderr(self, msg):
- server.PromptServer.instance.send_sync("manager-terminal-feedback", {"data": msg})
+ PromptServer.instance.send_sync("manager-terminal-feedback", {"data": msg})
def write_stdout(self, msg):
- server.PromptServer.instance.send_sync("manager-terminal-feedback", {"data": msg})
+ PromptServer.instance.send_sync("manager-terminal-feedback", {"data": msg})
manager_terminal_hook = ManagerTerminalHook()
-@server.PromptServer.instance.routes.get("/manager/terminal")
+@PromptServer.instance.routes.get("/manager/terminal")
async def terminal_mode(request):
if "mode" in request.rel_url.query:
if request.rel_url.query['mode'] == 'true':
@@ -1897,72 +1094,72 @@ async def terminal_mode(request):
return web.Response(status=200)
-@server.PromptServer.instance.routes.get("/manager/preview_method")
+@PromptServer.instance.routes.get("/manager/preview_method")
async def preview_method(request):
if "value" in request.rel_url.query:
set_preview_method(request.rel_url.query['value'])
- write_config()
+ core.write_config()
else:
- return web.Response(text=get_current_preview_method(), status=200)
+ return web.Response(text=core.manager_funcs.get_current_preview_method(), status=200)
return web.Response(status=200)
-@server.PromptServer.instance.routes.get("/manager/badge_mode")
+@PromptServer.instance.routes.get("/manager/badge_mode")
async def badge_mode(request):
if "value" in request.rel_url.query:
set_badge_mode(request.rel_url.query['value'])
- write_config()
+ core.write_config()
else:
- return web.Response(text=get_config()['badge_mode'], status=200)
+ return web.Response(text=core.get_config()['badge_mode'], status=200)
return web.Response(status=200)
-@server.PromptServer.instance.routes.get("/manager/default_ui")
+@PromptServer.instance.routes.get("/manager/default_ui")
async def default_ui_mode(request):
if "value" in request.rel_url.query:
set_default_ui_mode(request.rel_url.query['value'])
- write_config()
+ core.write_config()
else:
- return web.Response(text=get_config()['default_ui'], status=200)
+ return web.Response(text=core.get_config()['default_ui'], status=200)
return web.Response(status=200)
-@server.PromptServer.instance.routes.get("/manager/component/policy")
+@PromptServer.instance.routes.get("/manager/component/policy")
async def component_policy(request):
if "value" in request.rel_url.query:
set_component_policy(request.rel_url.query['value'])
- write_config()
+ core.write_config()
else:
- return web.Response(text=get_config()['component_policy'], status=200)
+ return web.Response(text=core.get_config()['component_policy'], status=200)
return web.Response(status=200)
-@server.PromptServer.instance.routes.get("/manager/dbl_click/policy")
+@PromptServer.instance.routes.get("/manager/dbl_click/policy")
async def dbl_click_policy(request):
if "value" in request.rel_url.query:
set_double_click_policy(request.rel_url.query['value'])
- write_config()
+ core.write_config()
else:
- return web.Response(text=get_config()['double_click_policy'], status=200)
+ return web.Response(text=core.get_config()['double_click_policy'], status=200)
return web.Response(status=200)
-@server.PromptServer.instance.routes.get("/manager/channel_url_list")
+@PromptServer.instance.routes.get("/manager/channel_url_list")
async def channel_url_list(request):
- channels = get_channel_dict()
+ channels = core.get_channel_dict()
if "value" in request.rel_url.query:
channel_url = channels.get(request.rel_url.query['value'])
if channel_url is not None:
- get_config()['channel_url'] = channel_url
- write_config()
+ core.get_config()['channel_url'] = channel_url
+ core.write_config()
else:
selected = 'custom'
- selected_url = get_config()['channel_url']
+ selected_url = core.get_config()['channel_url']
for name, url in channels.items():
if url == selected_url:
@@ -1970,13 +1167,13 @@ async def channel_url_list(request):
break
res = {'selected': selected,
- 'list': get_channel_list()}
+ 'list': core.get_channel_list()}
return web.json_response(res, status=200)
return web.Response(status=200)
-@server.PromptServer.instance.routes.get("/manager/notice")
+@PromptServer.instance.routes.get("/manager/notice")
async def get_notice(request):
url = "github.com"
path = "/ltdrdata/ltdrdata.github.io/wiki/News"
@@ -1997,7 +1194,7 @@ async def get_notice(request):
markdown_content += f"
Manager: {version_str}"
try:
- if comfy_ui_required_commit_datetime.date() > comfy_ui_commit_datetime.date():
+ if core.comfy_ui_required_commit_datetime.date() > comfy_ui_commit_datetime.date():
markdown_content = f'
Your ComfyUI is too OUTDATED!!!
' + markdown_content except: pass @@ -2009,7 +1206,7 @@ async def get_notice(request): return web.Response(text="Unable to retrieve Notice", status=200) -@server.PromptServer.instance.routes.get("/manager/reboot") +@PromptServer.instance.routes.get("/manager/reboot") def restart(self): try: sys.stdout.close_log() @@ -2025,7 +1222,7 @@ def sanitize_filename(input_string): return result_string -@server.PromptServer.instance.routes.post("/manager/component/save") +@PromptServer.instance.routes.post("/manager/component/save") async def save_component(request): try: data = await request.json() @@ -2055,7 +1252,7 @@ async def save_component(request): return web.Response(status=400) -@server.PromptServer.instance.routes.post("/manager/component/loads") +@PromptServer.instance.routes.post("/manager/component/loads") async def load_components(request): try: json_files = [f for f in os.listdir(components_path) if f.endswith('.json')] @@ -2077,22 +1274,22 @@ async def load_components(request): return web.Response(status=400) -@server.PromptServer.instance.routes.get("/manager/share_option") +@PromptServer.instance.routes.get("/manager/share_option") async def share_option(request): if "value" in request.rel_url.query: - get_config()['share_option'] = request.rel_url.query['value'] - write_config() + core.get_config()['share_option'] = request.rel_url.query['value'] + core.write_config() else: - return web.Response(text=get_config()['share_option'], status=200) + return web.Response(text=core.get_config()['share_option'], status=200) return web.Response(status=200) def get_openart_auth(): - if not os.path.exists(os.path.join(comfyui_manager_path, ".openart_key")): + if not os.path.exists(os.path.join(core.comfyui_manager_path, ".openart_key")): return None try: - with open(os.path.join(comfyui_manager_path, ".openart_key"), "r") as f: + with open(os.path.join(core.comfyui_manager_path, ".openart_key"), "r") as f: openart_key = f.read().strip() return openart_key if openart_key else None except: @@ -2100,10 +1297,10 @@ def get_openart_auth(): def get_matrix_auth(): - if not os.path.exists(os.path.join(comfyui_manager_path, "matrix_auth")): + if not os.path.exists(os.path.join(core.comfyui_manager_path, "matrix_auth")): return None try: - with open(os.path.join(comfyui_manager_path, "matrix_auth"), "r") as f: + with open(os.path.join(core.comfyui_manager_path, "matrix_auth"), "r") as f: matrix_auth = f.read() homeserver, username, password = matrix_auth.strip().split("\n") if not homeserver or not username or not password: @@ -2118,10 +1315,10 @@ def get_matrix_auth(): def get_comfyworkflows_auth(): - if not os.path.exists(os.path.join(comfyui_manager_path, "comfyworkflows_sharekey")): + if not os.path.exists(os.path.join(core.comfyui_manager_path, "comfyworkflows_sharekey")): return None try: - with open(os.path.join(comfyui_manager_path, "comfyworkflows_sharekey"), "r") as f: + with open(os.path.join(core.comfyui_manager_path, "comfyworkflows_sharekey"), "r") as f: share_key = f.read() if not share_key.strip(): return None @@ -2131,10 +1328,10 @@ def get_comfyworkflows_auth(): def get_youml_settings(): - if not os.path.exists(os.path.join(comfyui_manager_path, ".youml")): + if not os.path.exists(os.path.join(core.comfyui_manager_path, ".youml")): return None try: - with open(os.path.join(comfyui_manager_path, ".youml"), "r") as f: + with open(os.path.join(core.comfyui_manager_path, ".youml"), "r") as f: youml_settings = f.read().strip() return youml_settings if youml_settings else None except: @@ -2142,11 +1339,11 @@ def get_youml_settings(): def set_youml_settings(settings): - with open(os.path.join(comfyui_manager_path, ".youml"), "w") as f: + with open(os.path.join(core.comfyui_manager_path, ".youml"), "w") as f: f.write(settings) -@server.PromptServer.instance.routes.get("/manager/get_openart_auth") +@PromptServer.instance.routes.get("/manager/get_openart_auth") async def api_get_openart_auth(request): # print("Getting stored Matrix credentials...") openart_key = get_openart_auth() @@ -2155,16 +1352,16 @@ async def api_get_openart_auth(request): return web.json_response({"openart_key": openart_key}) -@server.PromptServer.instance.routes.post("/manager/set_openart_auth") +@PromptServer.instance.routes.post("/manager/set_openart_auth") async def api_set_openart_auth(request): json_data = await request.json() openart_key = json_data['openart_key'] - with open(os.path.join(comfyui_manager_path, ".openart_key"), "w") as f: + with open(os.path.join(core.comfyui_manager_path, ".openart_key"), "w") as f: f.write(openart_key) return web.Response(status=200) -@server.PromptServer.instance.routes.get("/manager/get_matrix_auth") +@PromptServer.instance.routes.get("/manager/get_matrix_auth") async def api_get_matrix_auth(request): # print("Getting stored Matrix credentials...") matrix_auth = get_matrix_auth() @@ -2173,7 +1370,7 @@ async def api_get_matrix_auth(request): return web.json_response(matrix_auth) -@server.PromptServer.instance.routes.get("/manager/youml/settings") +@PromptServer.instance.routes.get("/manager/youml/settings") async def api_get_youml_settings(request): youml_settings = get_youml_settings() if not youml_settings: @@ -2181,14 +1378,14 @@ async def api_get_youml_settings(request): return web.json_response(json.loads(youml_settings)) -@server.PromptServer.instance.routes.post("/manager/youml/settings") +@PromptServer.instance.routes.post("/manager/youml/settings") async def api_set_youml_settings(request): json_data = await request.json() set_youml_settings(json.dumps(json_data)) return web.Response(status=200) -@server.PromptServer.instance.routes.get("/manager/get_comfyworkflows_auth") +@PromptServer.instance.routes.get("/manager/get_comfyworkflows_auth") async def api_get_comfyworkflows_auth(request): # Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken' # in the same directory as the ComfyUI base folder @@ -2199,25 +1396,25 @@ async def api_get_comfyworkflows_auth(request): return web.json_response({"comfyworkflows_sharekey" : comfyworkflows_auth}) args.enable_cors_header = "*" -if hasattr(server.PromptServer.instance, "app"): - app = server.PromptServer.instance.app +if hasattr(PromptServer.instance, "app"): + app = PromptServer.instance.app cors_middleware = server.create_cors_middleware(args.enable_cors_header) app.middlewares.append(cors_middleware) -@server.PromptServer.instance.routes.post("/manager/set_esheep_workflow_and_images") +@PromptServer.instance.routes.post("/manager/set_esheep_workflow_and_images") async def set_esheep_workflow_and_images(request): json_data = await request.json() current_workflow = json_data['workflow'] images = json_data['images'] - with open(os.path.join(comfyui_manager_path, "esheep_share_message.json"), "w", encoding='utf-8') as file: + with open(os.path.join(core.comfyui_manager_path, "esheep_share_message.json"), "w", encoding='utf-8') as file: json.dump(json_data, file, indent=4) return web.Response(status=200) -@server.PromptServer.instance.routes.get("/manager/get_esheep_workflow_and_images") +@PromptServer.instance.routes.get("/manager/get_esheep_workflow_and_images") async def get_esheep_workflow_and_images(request): - with open(os.path.join(comfyui_manager_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file: + with open(os.path.join(core.comfyui_manager_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file: data = json.load(file) return web.Response(status=200, text=json.dumps(data)) @@ -2226,12 +1423,12 @@ def set_matrix_auth(json_data): homeserver = json_data['homeserver'] username = json_data['username'] password = json_data['password'] - with open(os.path.join(comfyui_manager_path, "matrix_auth"), "w") as f: + with open(os.path.join(core.comfyui_manager_path, "matrix_auth"), "w") as f: f.write("\n".join([homeserver, username, password])) def set_comfyworkflows_auth(comfyworkflows_sharekey): - with open(os.path.join(comfyui_manager_path, "comfyworkflows_sharekey"), "w") as f: + with open(os.path.join(core.comfyui_manager_path, "comfyworkflows_sharekey"), "w") as f: f.write(comfyworkflows_sharekey) @@ -2243,7 +1440,6 @@ def has_provided_comfyworkflows_auth(comfyworkflows_sharekey): return comfyworkflows_sharekey.strip() - def extract_model_file_names(json_data): """Extract unique file names from the input JSON data.""" file_names = set() @@ -2287,7 +1483,7 @@ def compute_sha256_checksum(filepath): return sha256.hexdigest() -@server.PromptServer.instance.routes.post("/manager/share") +@PromptServer.instance.routes.post("/manager/share") async def share_art(request): # get json data json_data = await request.json() @@ -2464,30 +1660,16 @@ def sanitize(data): return data.replace("<", "<").replace(">", ">") -def lookup_customnode_by_url(data, target): - for x in data['custom_nodes']: - if target in x['files']: - dir_name = os.path.splitext(os.path.basename(target))[0].replace(".git", "") - dir_path = os.path.join(custom_nodes_path, dir_name) - if os.path.exists(dir_path): - x['installed'] = 'True' - elif os.path.exists(dir_path + ".disabled"): - x['installed'] = 'Disabled' - return x - - return None - - async def _confirm_try_install(sender, custom_node_url, msg): - json_obj = await get_data_by_mode('default', 'custom-node-list.json') + json_obj = await core.get_data_by_mode('default', 'custom-node-list.json') sender = sanitize(sender) msg = sanitize(msg) - target = lookup_customnode_by_url(json_obj, custom_node_url) + target = core.lookup_customnode_by_url(json_obj, custom_node_url) if target is not None: - server.PromptServer.instance.send_sync("cm-api-try-install-customnode", - {"sender": sender, "target": target, "msg": msg}) + PromptServer.instance.send_sync("cm-api-try-install-customnode", + {"sender": sender, "target": target, "msg": msg}) else: print(f"[ComfyUI Manager API] Failed to try install - Unknown custom node url '{custom_node_url}'") @@ -2500,15 +1682,17 @@ cm_global.register_api('cm.try-install-custom-node', confirm_try_install) import asyncio + + async def default_cache_update(): async def get_cache(filename): uri = 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/' + filename - cache_uri = str(simple_hash(uri)) + '_' + filename - cache_uri = os.path.join(cache_dir, cache_uri) + cache_uri = str(core.simple_hash(uri)) + '_' + filename + cache_uri = os.path.join(core.cache_dir, cache_uri) - json_obj = await get_data(uri, True) + json_obj = await core.get_data(uri, True) - with cache_lock: + with core.cache_lock: with open(cache_uri, "w", encoding='utf-8') as file: json.dump(json_obj, file, indent=4, sort_keys=True) print(f"[ComfyUI-Manager] default cache updated: {uri}") @@ -2524,9 +1708,9 @@ async def default_cache_update(): threading.Thread(target=lambda: asyncio.run(default_cache_update())).start() -if not os.path.exists(config_path): - get_config() - write_config() +if not os.path.exists(core.config_path): + core.get_config() + core.write_config() WEB_DIRECTORY = "js" diff --git a/glob/manager_core.py b/glob/manager_core.py new file mode 100644 index 0000000..e2d2e34 --- /dev/null +++ b/glob/manager_core.py @@ -0,0 +1,834 @@ +import os +import sys +import subprocess +import re +import shutil +import configparser +import platform +from datetime import datetime +import git +from git.remote import RemoteProgress +from urllib.parse import urlparse +from tqdm.auto import tqdm +import aiohttp +import threading +import json +import time + +glob_path = os.path.join(os.path.dirname(__file__), "glob") +sys.path.append(glob_path) + +import cm_global +from manager_util import * + +comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +custom_nodes_path = os.path.abspath(os.path.join(comfyui_manager_path, '..')) +channel_list_path = os.path.join(comfyui_manager_path, 'channels.list') +config_path = os.path.join(custom_nodes_path, "config.ini") +startup_script_path = os.path.join(comfyui_manager_path, "startup-scripts") +git_script_path = os.path.join(custom_nodes_path, "git_helper.py") +cache_dir = os.path.join(comfyui_manager_path, '.cache') +cached_config = None +js_path = None + +comfy_ui_required_revision = 1930 +comfy_ui_required_commit_datetime = datetime(2024, 1, 24, 0, 0, 0) + +comfy_ui_revision = "Unknown" +comfy_ui_commit_datetime = datetime(1900, 1, 1, 0, 0, 0) + + +cache_lock = threading.Lock() + + +channel_dict = None +channel_list = None +pip_map = None + + +def remap_pip_package(pkg): + if pkg in cm_global.pip_overrides: + res = cm_global.pip_overrides[pkg] + print(f"[ComfyUI-Manager] '{pkg}' is remapped to '{res}'") + return res + else: + return pkg + + +def get_installed_packages(): + global pip_map + + if pip_map is None: + try: + result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True) + + pip_map = {} + for line in result.split('\n'): + x = line.strip() + if x: + y = line.split() + if y[0] == 'Package' or y[0].startswith('-'): + continue + + pip_map[y[0]] = y[1] + except subprocess.CalledProcessError as e: + print(f"[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.") + return set() + + return pip_map + + +def clear_pip_cache(): + global pip_map + pip_map = None + + +def is_blacklisted(name): + name = name.strip() + + pattern = r'([^<>!=]+)([<>!=]=?)(.*)' + match = re.search(pattern, name) + + if match: + name = match.group(1) + + if name in cm_global.pip_downgrade_blacklist: + pips = get_installed_packages() + + if match is None: + if name in pips: + return True + elif match.group(2) in ['<=', '==', '<']: + if name in pips: + if StrictVersion(pips[name]) >= StrictVersion(match.group(3)): + return True + + return False + + +def get_channel_dict(): + global channel_dict + + if channel_dict is None: + channel_dict = {} + + if not os.path.exists(channel_list_path): + shutil.copy(channel_list_path+'.template', channel_list_path) + + with open(os.path.join(comfyui_manager_path, 'channels.list'), 'r') as file: + channels = file.read() + for x in channels.split('\n'): + channel_info = x.split("::") + if len(channel_info) == 2: + channel_dict[channel_info[0]] = channel_info[1] + + return channel_dict + + +def get_channel_list(): + global channel_list + + if channel_list is None: + channel_list = [] + for k, v in get_channel_dict().items(): + channel_list.append(f"{k}::{v}") + + return channel_list + + +class ManagerFuncs: + def __init__(self): + pass + + def get_current_preview_method(self): + return "none" + + def run_script(self, cmd, cwd='.'): + if len(cmd) > 0 and cmd[0].startswith("#"): + print(f"[ComfyUI-Manager] Unexpected behavior: `{cmd}`") + return 0 + + out = subprocess.check_call(cmd, cwd=cwd) + print(out) + + return 0 + + +manager_funcs = ManagerFuncs() + + +def write_config(): + config = configparser.ConfigParser() + config['default'] = { + 'preview_method': manager_funcs.get_current_preview_method(), + 'badge_mode': get_config()['badge_mode'], + 'git_exe': get_config()['git_exe'], + 'channel_url': get_config()['channel_url'], + 'share_option': get_config()['share_option'], + 'bypass_ssl': get_config()['bypass_ssl'], + "file_logging": get_config()['file_logging'], + 'default_ui': get_config()['default_ui'], + 'component_policy': get_config()['component_policy'], + 'double_click_policy': get_config()['double_click_policy'], + 'windows_selector_event_loop_policy': get_config()['windows_selector_event_loop_policy'], + 'model_download_by_agent': get_config()['model_download_by_agent'], + 'downgrade_blacklist': get_config()['downgrade_blacklist'] + } + with open(config_path, 'w') as configfile: + config.write(configfile) + + +def read_config(): + try: + config = configparser.ConfigParser() + config.read(config_path) + default_conf = config['default'] + + return { + 'preview_method': default_conf['preview_method'] if 'preview_method' in default_conf else manager_funcs.get_current_preview_method(), + 'badge_mode': default_conf['badge_mode'] if 'badge_mode' in default_conf else 'none', + 'git_exe': default_conf['git_exe'] if 'git_exe' in default_conf else '', + 'channel_url': default_conf['channel_url'] if 'channel_url' in default_conf else 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main', + 'share_option': default_conf['share_option'] if 'share_option' in default_conf else 'all', + 'bypass_ssl': default_conf['bypass_ssl'] if 'bypass_ssl' in default_conf else False, + 'file_logging': default_conf['file_logging'] if 'file_logging' in default_conf else True, + 'default_ui': default_conf['default_ui'] if 'default_ui' in default_conf else 'none', + 'component_policy': default_conf['component_policy'] if 'component_policy' in default_conf else 'workflow', + 'double_click_policy': default_conf['double_click_policy'] if 'double_click_policy' in default_conf else 'copy-all', + 'windows_selector_event_loop_policy': default_conf['windows_selector_event_loop_policy'] if 'windows_selector_event_loop_policy' in default_conf else False, + 'model_download_by_agent': default_conf['model_download_by_agent'] if 'model_download_by_agent' in default_conf else False, + 'downgrade_blacklist': default_conf['downgrade_blacklist'] if 'downgrade_blacklist' in default_conf else '', + } + + except Exception: + return { + 'preview_method': manager_funcs.get_current_preview_method(), + 'badge_mode': 'none', + 'git_exe': '', + 'channel_url': 'https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main', + 'share_option': 'all', + 'bypass_ssl': False, + 'file_logging': True, + 'default_ui': 'none', + 'component_policy': 'workflow', + 'double_click_policy': 'copy-all', + 'windows_selector_event_loop_policy': False, + 'model_download_by_agent': False, + 'downgrade_blacklist': '' + } + + +def get_config(): + global cached_config + + if cached_config is None: + cached_config = read_config() + + return cached_config + + +def switch_to_default_branch(repo): + show_result = repo.git.remote("show", "origin") + matches = re.search(r"\s*HEAD branch:\s*(.*)", show_result) + if matches: + default_branch = matches.group(1) + repo.git.checkout(default_branch) + + +def try_install_script(url, repo_path, install_cmd, instant_execution=False): + if not instant_execution and ((len(install_cmd) > 0 and install_cmd[0].startswith('#')) or (platform.system() == "Windows" and comfy_ui_commit_datetime.date() >= comfy_ui_required_commit_datetime.date())): + if not os.path.exists(startup_script_path): + os.makedirs(startup_script_path) + + script_path = os.path.join(startup_script_path, "install-scripts.txt") + with open(script_path, "a") as file: + obj = [repo_path] + install_cmd + file.write(f"{obj}\n") + + return True + else: + if len(install_cmd) == 5 and install_cmd[2:4] == ['pip', 'install']: + if is_blacklisted(install_cmd[4]): + print(f"[ComfyUI-Manager] skip black listed pip installation: '{install_cmd[4]}'") + return True + + print(f"\n## ComfyUI-Manager: EXECUTE => {install_cmd}") + code = manager_funcs.run_script(install_cmd, cwd=repo_path) + + if platform.system() == "Windows": + try: + if comfy_ui_commit_datetime.date() < comfy_ui_required_commit_datetime.date(): + print("\n\n###################################################################") + print(f"[WARN] ComfyUI-Manager: Your ComfyUI version ({comfy_ui_revision})[{comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version.") + print(f"[WARN] The extension installation feature may not work properly in the current installed ComfyUI version on Windows environment.") + print("###################################################################\n\n") + except: + pass + + if code != 0: + if url is None: + url = os.path.dirname(repo_path) + print(f"install script failed: {url}") + return False + + +# use subprocess to avoid file system lock by git (Windows) +def __win_check_git_update(path, do_fetch=False, do_update=False): + if do_fetch: + command = [sys.executable, git_script_path, "--fetch", path] + elif do_update: + command = [sys.executable, git_script_path, "--pull", path] + else: + command = [sys.executable, git_script_path, "--check", path] + + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, _ = process.communicate() + output = output.decode('utf-8').strip() + + if 'detected dubious' in output: + # fix and try again + safedir_path = path.replace('\\', '/') + try: + print(f"[ComfyUI-Manager] Try fixing 'dubious repository' error on '{safedir_path}' repo") + process = subprocess.Popen(['git', 'config', '--global', '--add', 'safe.directory', safedir_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, _ = process.communicate() + + process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, _ = process.communicate() + output = output.decode('utf-8').strip() + except Exception: + print(f'[ComfyUI-Manager] failed to fixing') + + if 'detected dubious' in output: + print(f'\n[ComfyUI-Manager] Failed to fixing repository setup. Please execute this command on cmd: \n' + f'-----------------------------------------------------------------------------------------\n' + f'git config --global --add safe.directory "{safedir_path}"\n' + f'-----------------------------------------------------------------------------------------\n') + + if do_update: + if "CUSTOM NODE PULL: Success" in output: + process.wait() + print(f"\rUpdated: {path}") + return True, True # updated + elif "CUSTOM NODE PULL: None" in output: + process.wait() + return False, True # there is no update + else: + print(f"\rUpdate error: {path}") + process.wait() + return False, False # update failed + else: + if "CUSTOM NODE CHECK: True" in output: + process.wait() + return True, True + elif "CUSTOM NODE CHECK: False" in output: + process.wait() + return False, True + else: + print(f"\rFetch error: {path}") + print(f"\n{output}\n") + process.wait() + return False, True + + +def __win_check_git_pull(path): + command = [sys.executable, git_script_path, "--pull", path] + process = subprocess.Popen(command) + process.wait() + + +def execute_install_script(url, repo_path, lazy_mode=False): + install_script_path = os.path.join(repo_path, "install.py") + requirements_path = os.path.join(repo_path, "requirements.txt") + + if lazy_mode: + install_cmd = ["#LAZY-INSTALL-SCRIPT", sys.executable] + try_install_script(url, repo_path, install_cmd) + else: + if os.path.exists(requirements_path): + print("Install: pip packages") + with open(requirements_path, "r") as requirements_file: + for line in requirements_file: + package_name = remap_pip_package(line.strip()) + if package_name: + install_cmd = [sys.executable, "-m", "pip", "install", package_name] + if package_name.strip() != "": + try_install_script(url, repo_path, install_cmd) + + if os.path.exists(install_script_path): + print(f"Install: install script") + install_cmd = [sys.executable, "install.py"] + try_install_script(url, repo_path, install_cmd) + + return True + + +def git_repo_has_updates(path, do_fetch=False, do_update=False): + if do_fetch: + print(f"\x1b[2K\rFetching: {path}", end='') + elif do_update: + print(f"\x1b[2K\rUpdating: {path}", end='') + + # Check if the path is a git repository + if not os.path.exists(os.path.join(path, '.git')): + raise ValueError('Not a git repository') + + if platform.system() == "Windows": + updated, success = __win_check_git_update(path, do_fetch, do_update) + if updated and success: + execute_install_script(None, path, lazy_mode=True) + return updated, success + else: + # Fetch the latest commits from the remote repository + repo = git.Repo(path) + + current_branch = repo.active_branch + branch_name = current_branch.name + + remote_name = 'origin' + remote = repo.remote(name=remote_name) + + # Get the current commit hash + commit_hash = repo.head.commit.hexsha + + if do_fetch or do_update: + remote.fetch() + + if do_update: + if repo.head.is_detached: + switch_to_default_branch(repo) + + remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha + + if commit_hash == remote_commit_hash: + repo.close() + return False, True + + try: + remote.pull() + repo.git.submodule('update', '--init', '--recursive') + new_commit_hash = repo.head.commit.hexsha + + if commit_hash != new_commit_hash: + execute_install_script(None, path) + print(f"\x1b[2K\rUpdated: {path}") + return True, True + else: + return False, False + + except Exception as e: + print(f"\nUpdating failed: {path}\n{e}", file=sys.stderr) + return False, False + + if repo.head.is_detached: + repo.close() + return True, True + + # Get commit hash of the remote branch + current_branch = repo.active_branch + branch_name = current_branch.name + + remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha + + # Compare the commit hashes to determine if the local repository is behind the remote repository + if commit_hash != remote_commit_hash: + # Get the commit dates + commit_date = repo.head.commit.committed_datetime + remote_commit_date = repo.refs[f'{remote_name}/{branch_name}'].object.committed_datetime + + # Compare the commit dates to determine if the local repository is behind the remote repository + if commit_date < remote_commit_date: + repo.close() + return True, True + + repo.close() + + return False, True + + +class GitProgress(RemoteProgress): + def __init__(self): + super().__init__() + self.pbar = tqdm() + + def update(self, op_code, cur_count, max_count=None, message=''): + self.pbar.total = max_count + self.pbar.n = cur_count + self.pbar.pos = 0 + self.pbar.refresh() + + +def is_valid_url(url): + try: + result = urlparse(url) + return all([result.scheme, result.netloc]) + except ValueError: + return False + + +def gitclone_install(files): + print(f"install: {files}") + for url in files: + if not is_valid_url(url): + print(f"Invalid git url: '{url}'") + return False + + if url.endswith("/"): + url = url[:-1] + try: + print(f"Download: git clone '{url}'") + repo_name = os.path.splitext(os.path.basename(url))[0] + repo_path = os.path.join(custom_nodes_path, repo_name) + + # Clone the repository from the remote URL + if platform.system() == 'Windows': + res = manager_funcs.run_script([sys.executable, git_script_path, "--clone", custom_nodes_path, url]) + if res != 0: + return False + else: + repo = git.Repo.clone_from(url, repo_path, recursive=True, progress=GitProgress()) + repo.git.clear_cache() + repo.close() + + if not execute_install_script(url, repo_path): + return False + + except Exception as e: + print(f"Install(git-clone) error: {url} / {e}", file=sys.stderr) + return False + + print("Installation was successful.") + return True + + +def git_pull(path): + # Check if the path is a git repository + if not os.path.exists(os.path.join(path, '.git')): + raise ValueError('Not a git repository') + + # Pull the latest changes from the remote repository + if platform.system() == "Windows": + return __win_check_git_pull(path) + else: + repo = git.Repo(path) + + if repo.is_dirty(): + repo.git.stash() + + if repo.head.is_detached: + switch_to_default_branch(repo) + + current_branch = repo.active_branch + remote_name = current_branch.tracking_branch().remote_name + remote = repo.remote(name=remote_name) + + remote.pull() + repo.git.submodule('update', '--init', '--recursive') + + repo.close() + + return True + + +async def get_data(uri, silent=False): + if not silent: + print(f"FETCH DATA from: {uri}") + + if uri.startswith("http"): + async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + async with session.get(uri) as resp: + json_text = await resp.text() + else: + with cache_lock: + with open(uri, "r", encoding="utf-8") as f: + json_text = f.read() + + json_obj = json.loads(json_text) + return json_obj + + +def simple_hash(input_string): + hash_value = 0 + for char in input_string: + hash_value = (hash_value * 31 + ord(char)) % (2**32) + + return hash_value + + +def is_file_created_within_one_day(file_path): + if not os.path.exists(file_path): + return False + + file_creation_time = os.path.getctime(file_path) + current_time = datetime.now().timestamp() + time_difference = current_time - file_creation_time + + return time_difference <= 86400 + + +async def get_data_by_mode(mode, filename): + try: + if mode == "local": + uri = os.path.join(comfyui_manager_path, filename) + json_obj = await get_data(uri) + else: + uri = get_config()['channel_url'] + '/' + filename + cache_uri = str(simple_hash(uri))+'_'+filename + cache_uri = os.path.join(cache_dir, cache_uri) + + if mode == "cache": + if is_file_created_within_one_day(cache_uri): + json_obj = await get_data(cache_uri) + else: + json_obj = await get_data(uri) + with cache_lock: + with open(cache_uri, "w", encoding='utf-8') as file: + json.dump(json_obj, file, indent=4, sort_keys=True) + else: + uri = get_config()['channel_url'] + '/' + filename + json_obj = await get_data(uri) + with cache_lock: + with open(cache_uri, "w", encoding='utf-8') as file: + json.dump(json_obj, file, indent=4, sort_keys=True) + except Exception as e: + print(f"[ComfyUI-Manager] Due to a network error, switching to local mode.\n=> {filename}\n=> {e}") + uri = os.path.join(comfyui_manager_path, filename) + json_obj = await get_data(uri) + + return json_obj + + +def gitclone_fix(files): + print(f"Try fixing: {files}") + for url in files: + if not is_valid_url(url): + print(f"Invalid git url: '{url}'") + return False + + if url.endswith("/"): + url = url[:-1] + try: + repo_name = os.path.splitext(os.path.basename(url))[0] + repo_path = os.path.join(custom_nodes_path, repo_name) + + if not execute_install_script(url, repo_path): + return False + + except Exception as e: + print(f"Install(git-clone) error: {url} / {e}", file=sys.stderr) + return False + + print(f"Attempt to fixing '{files}' is done.") + return True + + +def pip_install(packages): + install_cmd = ['#FORCE', sys.executable, "-m", "pip", "install", '-U'] + packages + try_install_script('pip install via manager', '..', install_cmd) + + +def rmtree(path): + retry_count = 3 + + while True: + try: + retry_count -= 1 + + if platform.system() == "Windows": + manager_funcs.run_script(['attrib', '-R', path + '\\*', '/S']) + shutil.rmtree(path) + + return True + + except Exception as ex: + print(f"ex: {ex}") + time.sleep(3) + + if retry_count < 0: + raise ex + + print(f"Uninstall retry({retry_count})") + + +def gitclone_uninstall(files): + import os + + print(f"uninstall: {files}") + for url in files: + if url.endswith("/"): + url = url[:-1] + try: + dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "") + dir_path = os.path.join(custom_nodes_path, dir_name) + + # safety check + if dir_path == '/' or dir_path[1:] == ":/" or dir_path == '': + print(f"Uninstall(git-clone) error: invalid path '{dir_path}' for '{url}'") + return False + + install_script_path = os.path.join(dir_path, "uninstall.py") + disable_script_path = os.path.join(dir_path, "disable.py") + if os.path.exists(install_script_path): + uninstall_cmd = [sys.executable, "uninstall.py"] + code = manager_funcs.run_script(uninstall_cmd, cwd=dir_path) + + if code != 0: + print(f"An error occurred during the execution of the uninstall.py script. Only the '{dir_path}' will be deleted.") + elif os.path.exists(disable_script_path): + disable_script = [sys.executable, "disable.py"] + code = manager_funcs.run_script(disable_script, cwd=dir_path) + if code != 0: + print(f"An error occurred during the execution of the disable.py script. Only the '{dir_path}' will be deleted.") + + if os.path.exists(dir_path): + rmtree(dir_path) + elif os.path.exists(dir_path + ".disabled"): + rmtree(dir_path + ".disabled") + except Exception as e: + print(f"Uninstall(git-clone) error: {url} / {e}", file=sys.stderr) + return False + + print("Uninstallation was successful.") + return True + + +def gitclone_set_active(files, is_disable): + import os + + if is_disable: + action_name = "Disable" + else: + action_name = "Enable" + + print(f"{action_name}: {files}") + for url in files: + if url.endswith("/"): + url = url[:-1] + try: + dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "") + dir_path = os.path.join(custom_nodes_path, dir_name) + + # safey check + if dir_path == '/' or dir_path[1:] == ":/" or dir_path == '': + print(f"{action_name}(git-clone) error: invalid path '{dir_path}' for '{url}'") + return False + + if is_disable: + current_path = dir_path + new_path = dir_path + ".disabled" + else: + current_path = dir_path + ".disabled" + new_path = dir_path + + os.rename(current_path, new_path) + + if is_disable: + if os.path.exists(os.path.join(new_path, "disable.py")): + disable_script = [sys.executable, "disable.py"] + try_install_script(url, new_path, disable_script) + else: + if os.path.exists(os.path.join(new_path, "enable.py")): + enable_script = [sys.executable, "enable.py"] + try_install_script(url, new_path, enable_script) + + except Exception as e: + print(f"{action_name}(git-clone) error: {url} / {e}", file=sys.stderr) + return False + + print(f"{action_name} was successful.") + return True + + +def gitclone_update(files): + import os + + print(f"Update: {files}") + for url in files: + if url.endswith("/"): + url = url[:-1] + try: + repo_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "") + repo_path = os.path.join(custom_nodes_path, repo_name) + git_pull(repo_path) + + if not execute_install_script(url, repo_path, lazy_mode=True): + return False + + except Exception as e: + print(f"Update(git-clone) error: {url} / {e}", file=sys.stderr) + return False + + print("Update was successful.") + return True + + +def lookup_customnode_by_url(data, target): + for x in data['custom_nodes']: + if target in x['files']: + dir_name = os.path.splitext(os.path.basename(target))[0].replace(".git", "") + dir_path = os.path.join(custom_nodes_path, dir_name) + if os.path.exists(dir_path): + x['installed'] = 'True' + elif os.path.exists(dir_path + ".disabled"): + x['installed'] = 'Disabled' + return x + + return None + + +def check_a_custom_node_installed(item, do_fetch=False, do_update_check=True, do_update=False): + item['installed'] = 'None' + + if item['install_type'] == 'git-clone' and len(item['files']) == 1: + url = item['files'][0] + + if url.endswith("/"): + url = url[:-1] + + dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "") + dir_path = os.path.join(custom_nodes_path, dir_name) + if os.path.exists(dir_path): + try: + item['installed'] = 'True' # default + + if cm_global.try_call(api="cm.is_import_failed_extension", name=dir_name): + item['installed'] = 'Fail' + + if do_update_check: + update_state, success = git_repo_has_updates(dir_path, do_fetch, do_update) + if (do_update_check or do_update) and update_state: + item['installed'] = 'Update' + elif do_update and not success: + item['installed'] = 'Fail' + except: + if cm_global.try_call(api="cm.is_import_failed_extension", name=dir_name): + item['installed'] = 'Fail' + else: + item['installed'] = 'True' + + elif os.path.exists(dir_path + ".disabled"): + item['installed'] = 'Disabled' + + else: + item['installed'] = 'False' + + elif item['install_type'] == 'copy' and len(item['files']) == 1: + dir_name = os.path.basename(item['files'][0]) + + if item['files'][0].endswith('.py'): + base_path = custom_nodes_path + elif 'js_path' in item: + base_path = os.path.join(js_path, item['js_path']) + else: + base_path = js_path + + file_path = os.path.join(base_path, dir_name) + if os.path.exists(file_path): + if cm_global.try_call(api="cm.is_import_failed_extension", name=dir_name): + item['installed'] = 'Fail' + else: + item['installed'] = 'True' + elif os.path.exists(file_path + ".disabled"): + item['installed'] = 'Disabled' + else: + item['installed'] = 'False' diff --git a/prestartup_script.py b/prestartup_script.py index f77465a..68d8a9b 100644 --- a/prestartup_script.py +++ b/prestartup_script.py @@ -14,7 +14,6 @@ glob_path = os.path.join(os.path.dirname(__file__), "glob") sys.path.append(glob_path) from manager_util import * - import cm_global