Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ github-stats-cache.json
pip_overrides.json
*.json
check2.sh
/venv/
2 changes: 1 addition & 1 deletion cm-cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def fix_node(node_spec_str, is_all=False, cnt_msg=''):
print(f"ERROR: f{res.msg}")


def uninstall_node(node_spec_str, is_all=False, cnt_msg=''):
def uninstall_node(node_spec_str: str, is_all: bool = False, cnt_msg: str = ''):
spec = node_spec_str.split('@')
if len(spec) == 2 and spec[1] == 'unknown':
node_name = spec[0]
Expand Down
6 changes: 3 additions & 3 deletions git_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def checkout_custom_node_hash(git_custom_node_infos):
repo_name_to_url[repo_name] = url

for path in os.listdir(working_directory):
if '@' in path or path.endswith("ComfyUI-Manager"):
if path.endswith("ComfyUI-Manager"):
continue

fullpath = os.path.join(working_directory, path)
Expand Down Expand Up @@ -467,5 +467,5 @@ def setup_environment():
except Exception as e:
print(e)
sys.exit(-1)


32 changes: 32 additions & 0 deletions glob/git_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os

import git


def is_git_repo(path: str) -> bool:
""" Check if the path is a git repository. """
try:
# Try to create a Repo object from the path
_ = git.Repo(path).git_dir
return True
except git.exc.InvalidGitRepositoryError:
return False


def get_commit_hash(fullpath):
git_head = os.path.join(fullpath, '.git', 'HEAD')
if os.path.exists(git_head):
with open(git_head) as f:
line = f.readline()

if line.startswith("ref: "):
ref = os.path.join(fullpath, '.git', line[5:].strip())
if os.path.exists(ref):
with open(ref) as f2:
return f2.readline().strip()
else:
return "unknown"
else:
return line

return "unknown"
144 changes: 33 additions & 111 deletions glob/manager_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import cnr_utils
import manager_util
import manager_downloader
from node_package import InstalledNodePackage


version_code = [3, 1]
Expand Down Expand Up @@ -104,13 +105,13 @@ def check(root):
if subdir in ['.disabled', '__pycache__']:
continue

if '@' in subdir:
spec = subdir.split('@')
if spec[1] in ['unknown', 'nightly']:
continue

if not os.path.exists(os.path.join(root, subdir, '.tracking')):
invalid_nodes[spec[0]] = os.path.join(root, subdir)
package = unified_manager.installed_node_packages.get(subdir)
if not package:
continue

if not package.isValid():
invalid_nodes[subdir] = package.fullpath

node_paths = folder_paths.get_folder_paths("custom_nodes")
for x in node_paths:
Expand Down Expand Up @@ -308,27 +309,10 @@ def with_postinstall(self, postinstall):
return self


def get_commit_hash(fullpath):
git_head = os.path.join(fullpath, '.git', 'HEAD')
if os.path.exists(git_head):
with open(git_head) as f:
line = f.readline()

if line.startswith("ref: "):
ref = os.path.join(fullpath, '.git', line[5:].strip())
if os.path.exists(ref):
with open(ref) as f2:
return f2.readline().strip()
else:
return "unknown"
else:
return line

return "unknown"


class UnifiedManager:
def __init__(self):
self.installed_node_packages: dict[str, InstalledNodePackage] = {}

self.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath
self.nightly_inactive_nodes = {} # node_id -> fullpath
self.unknown_inactive_nodes = {} # node_id -> repo url * fullpath
Expand Down Expand Up @@ -462,94 +446,26 @@ def resolve_ver(self, fullpath):
else:
return "unknown"

def resolve_id_from_repo(self, fullpath):
git_config_path = os.path.join(fullpath, '.git', 'config')

if not os.path.exists(git_config_path):
return None

config = configparser.ConfigParser()
config.read(git_config_path)

for k, v in config.items():
if k.startswith('remote ') and 'url' in v:
cnr = self.get_cnr_by_repo(v['url'])
if cnr:
return "nightly", cnr['id'], v['url']
else:
return "unknown", v['url'].split('/')[-1], v['url']

def resolve_unknown(self, node_id, fullpath):
res = self.resolve_id_from_repo(fullpath)

if res is None:
self.unknown_inactive_nodes[node_id] = '', fullpath
return

ver_spec, node_id, url = res
def update_cache_at_path(self, fullpath):
node_package = InstalledNodePackage.from_fullpath(fullpath)
self.installed_node_packages[node_package.id] = node_package

if ver_spec == 'nightly':
self.nightly_inactive_nodes[node_id] = fullpath
else:
self.unknown_inactive_nodes[node_id] = url, fullpath
if node_package.is_disabled and node_package.is_unknown:
# NOTE: unknown package does not have a url.
self.unknown_inactive_nodes[node_package.id] = ('', node_package.fullpath)

def update_cache_at_path(self, fullpath, is_disabled):
name = os.path.basename(fullpath)
if node_package.is_disabled and node_package.is_nightly:
self.nightly_inactive_nodes[node_package.id] = node_package.fullpath

if name.endswith(".disabled"):
node_spec = name[:-9]
is_disabled = True
else:
node_spec = name
if node_package.is_enabled:
self.active_nodes[node_package.id] = node_package.version, node_package.fullpath

if '@' in node_spec:
node_spec = node_spec.split('@')
node_id = node_spec[0]
if node_id is None:
node_version = 'unknown'
else:
node_version = node_spec[1].replace("_", ".")

if node_version != 'unknown':
if node_id not in self.cnr_map:
# fallback
v = node_version

self.cnr_map[node_id] = {
'id': node_id,
'name': node_id,
'latest_version': {'version': v},
'publisher': {'id': 'N/A', 'name': 'N/A'}
}

elif node_version == 'unknown':
res = self.resolve_id_from_repo(fullpath)
if res is None:
print(f"Custom node unresolved: {fullpath}")
return

node_version, node_id, _ = res
else:
res = self.resolve_id_from_repo(fullpath)
if res is None:
print(f"Custom node unresolved: {fullpath}")
return
if node_package.is_enabled and node_package.is_unknown:
# NOTE: unknown package does not have a url.
self.unknown_active_nodes[node_package.id] = ('', node_package.fullpath)

node_version, node_id, _ = res

if not is_disabled:
# active nodes
if node_version == 'unknown':
self.unknown_active_nodes[node_id] = node_version, fullpath
else:
self.active_nodes[node_id] = node_version, fullpath
else:
if node_version == 'unknown':
self.resolve_unknown(node_id, fullpath)
elif node_version == 'nightly':
self.nightly_inactive_nodes[node_id] = fullpath
else:
self.add_to_cnr_inactive_nodes(node_id, node_version, fullpath)
if node_package.is_from_cnr and node_package.is_disabled:
self.add_to_cnr_inactive_nodes(node_package.id, node_package.version, node_package.fullpath)

def is_updatable(self, node_id):
cur_ver = self.get_cnr_active_version(node_id)
Expand Down Expand Up @@ -722,7 +638,7 @@ async def reload(self, cache_mode):
fullpath = os.path.join(custom_nodes_path, x)
if os.path.isdir(fullpath):
if x not in ['__pycache__', '.disabled']:
self.update_cache_at_path(fullpath, is_disabled=False)
self.update_cache_at_path(fullpath)

# reload node status info from custom_nodes/.disabled/*
for custom_nodes_path in folder_paths.get_folder_paths('custom_nodes'):
Expand All @@ -731,7 +647,7 @@ async def reload(self, cache_mode):
for x in os.listdir(disabled_dir):
fullpath = os.path.join(disabled_dir, x)
if os.path.isdir(fullpath):
self.update_cache_at_path(fullpath, is_disabled=True)
self.update_cache_at_path(fullpath)

@staticmethod
async def load_nightly(channel, mode):
Expand Down Expand Up @@ -890,6 +806,7 @@ def cnr_switch_version_lazy(self, node_id, version_spec=None, no_deps=False, ret

zip_url = node_info.download_url
from_path = self.active_nodes[node_id][1]
# PTAL(@ltdrdata): how to redesign and drop version_spec here?
target = f"{node_id}@{version_spec.replace('.', '_')}"
to_path = os.path.join(get_default_custom_nodes_path(), target)

Expand Down Expand Up @@ -957,6 +874,7 @@ def cnr_switch_version_instant(self, node_id, version_spec=None, instant_executi
os.rmdir(x)

# 5. rename dir name <node_id>@<prev_ver> ==> <node_id>@<cur_ver>
# PTAL(@ltdrdata): how to redesign and drop version_spec here
new_install_path = os.path.join(get_default_custom_nodes_path(), f"{node_id}@{version_spec.replace('.', '_')}")
print(f"'{install_path}' is moved to '{new_install_path}'")
shutil.move(install_path, new_install_path)
Expand Down Expand Up @@ -1038,6 +956,7 @@ def unified_enable(self, node_id, version_spec=None):

from_path = cnr_info[version_spec]
base_path = extract_base_custom_nodes_dir(from_path)
# PTAL(@ltdrdata): how to redesign and drop version_spec here
to_path = os.path.join(base_path, f"{node_id}@{version_spec.replace('.', '_')}")

if from_path is None or not os.path.exists(from_path):
Expand Down Expand Up @@ -1099,6 +1018,7 @@ def unified_disable(self, node_id, is_unknown):
return result.fail(f'Specified active node not exists: {node_id}')

base_path = extract_base_custom_nodes_dir(ver_and_path[1])
# PTAL(@ltdrdata): how to redesign and drop version_spec here
to_path = os.path.join(base_path, '.disabled', f"{node_id}@{ver_and_path[0].replace('.', '_')}")
shutil.move(ver_and_path[1], to_path)
result.append((ver_and_path[1], to_path))
Expand All @@ -1112,7 +1032,7 @@ def unified_disable(self, node_id, is_unknown):

return result

def unified_uninstall(self, node_id, is_unknown):
def unified_uninstall(self, node_id: str, is_unknown: bool):
"""
Remove whole installed custom nodes including inactive nodes
"""
Expand Down Expand Up @@ -1189,6 +1109,7 @@ def cnr_install(self, node_id, version_spec=None, instant_execution=False, no_de
os.remove(download_path)

# install_path
# PTAL(@ltdrdata): how to redesign and drop version_spec here
install_path = os.path.join(get_default_custom_nodes_path(), f"{node_id}@{version_spec.replace('.', '_')}")
if os.path.exists(install_path):
return result.fail(f'Install path already exists: {install_path}')
Expand Down Expand Up @@ -1375,6 +1296,7 @@ async def install_by_id(self, node_id, version_spec=None, channel=None, mode=Non
if version_spec == 'unknown':
to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), node_id)) # don't attach @unknown
else:
# PTAL(@ltdrdata): how to redesign and drop version_spec here
to_path = os.path.abspath(os.path.join(get_default_custom_nodes_path(), f"{node_id}@{version_spec.replace('.', '_')}"))
res = self.repo_install(repo_url, to_path, instant_execution=instant_execution, no_deps=no_deps, return_postinstall=return_postinstall)
if res.result:
Expand Down
35 changes: 5 additions & 30 deletions glob/manager_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,37 +544,12 @@ def populate_markdown(x):

@routes.get("/customnode/installed")
async def installed_list(request):
result = {}
for x in folder_paths.get_folder_paths('custom_nodes'):
for module_name in os.listdir(x):
if (
module_name.endswith('.disabled') or
module_name == '__pycache__' or
module_name.endswith('.py') or
module_name.endswith('.example') or
module_name.endswith('.pyc')
):
continue

spec = module_name.split('@')

if len(spec) == 2:
node_package_name = spec[0]
ver = spec[1].replace('_', '.')

if ver == 'nightly':
ver = None
else:
node_package_name = module_name
ver = None

# extract commit hash
if ver is None:
ver = core.get_commit_hash(os.path.join(x, node_package_name))
unified_manager = core.unified_manager

result[node_package_name] = ver

return web.json_response(result, content_type='application/json')
return web.json_response({
node_id: package.version if package.is_from_cnr else package.get_commit_hash()
for node_id, package in unified_manager.installed_node_packages.items()
}, content_type='application/json')


@routes.get("/customnode/getlist")
Expand Down
Loading
Loading