Skip to content
Open
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
54 changes: 12 additions & 42 deletions comfyui_manager/common/manager_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import logging
import platform
import shlex

from packaging import version

cache_lock = threading.Lock()
session_lock = threading.Lock()
Expand Down Expand Up @@ -58,62 +58,32 @@ def make_pip_cmd(cmd):
# print(f"[ComfyUI-Manager] 'distutils' package not found. Activating fallback mode for compatibility.")
class StrictVersion:
def __init__(self, version_string):
self.obj = version.parse(version_string)
self.version_string = version_string
self.major = 0
self.minor = 0
self.patch = 0
self.pre_release = None
self.parse_version_string()

def parse_version_string(self):
parts = self.version_string.split('.')
if not parts:
raise ValueError("Version string must not be empty")

self.major = int(parts[0])
self.minor = int(parts[1]) if len(parts) > 1 else 0
self.patch = int(parts[2]) if len(parts) > 2 else 0

# Handling pre-release versions if present
if len(parts) > 3:
self.pre_release = parts[3]
self.major = self.obj.major
self.minor = self.obj.minor
self.patch = self.obj.micro

def __str__(self):
version = f"{self.major}.{self.minor}.{self.patch}"
if self.pre_release:
version += f"-{self.pre_release}"
return version
return self.version_string

def __eq__(self, other):
return (self.major, self.minor, self.patch, self.pre_release) == \
(other.major, other.minor, other.patch, other.pre_release)
return self.obj == other.obj

def __lt__(self, other):
if (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch):
return self.pre_release_compare(self.pre_release, other.pre_release) < 0
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)

@staticmethod
def pre_release_compare(pre1, pre2):
if pre1 == pre2:
return 0
if pre1 is None:
return 1
if pre2 is None:
return -1
return -1 if pre1 < pre2 else 1
return self.obj < other.obj

def __le__(self, other):
return self == other or self < other
return self.obj == other.obj or self.obj < other.obj

def __gt__(self, other):
return not self <= other
return not self.obj <= other.obj

def __ge__(self, other):
return not self < other
return not self.obj < other.obj

def __ne__(self, other):
return not self == other
return not self.obj == other.obj


def simple_hash(input_string):
Expand Down
136 changes: 136 additions & 0 deletions comfyui_manager/common/snapshot_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
from . import manager_util
from . import git_utils
import json
import yaml
import logging

def read_snapshot(snapshot_path):
try:

with open(snapshot_path, 'r', encoding="UTF-8") as snapshot_file:
if snapshot_path.endswith('.json'):
info = json.load(snapshot_file)
elif snapshot_path.endswith('.yaml'):
info = yaml.load(snapshot_file, Loader=yaml.SafeLoader)
info = info['custom_nodes']

return info
except Exception as e:
logging.warning(f"Failed to read snapshot file: {snapshot_path}\nError: {e}")

return None


def diff_snapshot(a, b):
if not a or not b:
return None

nodepack_diff = {
'added': {},
'removed': [],
'upgraded': {},
'downgraded': {},
'changed': []
}

pip_diff = {
'added': {},
'upgraded': {},
'downgraded': {}
}

# check: comfyui
if a.get('comfyui') != b.get('comfyui'):
nodepack_diff['changed'].append('comfyui')

# check: cnr nodes
a_cnrs = a.get('cnr_custom_nodes', {})
b_cnrs = b.get('cnr_custom_nodes', {})

if 'comfyui-manager' in a_cnrs:
del a_cnrs['comfyui-manager']
if 'comfyui-manager' in b_cnrs:
del b_cnrs['comfyui-manager']

for k, v in a_cnrs.items():
if k not in b_cnrs.keys():
nodepack_diff['removed'].append(k)
elif a_cnrs[k] != b_cnrs[k]:
a_ver = manager_util.StrictVersion(a_cnrs[k])
b_ver = manager_util.StrictVersion(b_cnrs[k])
if a_ver < b_ver:
nodepack_diff['upgraded'][k] = {'from': a_cnrs[k], 'to': b_cnrs[k]}
elif a_ver > b_ver:
nodepack_diff['downgraded'][k] = {'from': a_cnrs[k], 'to': b_cnrs[k]}

added_cnrs = set(b_cnrs.keys()) - set(a_cnrs.keys())
for k in added_cnrs:
nodepack_diff['added'][k] = b_cnrs[k]

# check: git custom nodes
a_gits = a.get('git_custom_nodes', {})
b_gits = b.get('git_custom_nodes', {})

a_gits = {git_utils.normalize_url(k): v for k, v in a_gits.items() if k.lower() != 'comfyui-manager'}
b_gits = {git_utils.normalize_url(k): v for k, v in b_gits.items() if k.lower() != 'comfyui-manager'}

for k, v in a_gits.items():
if k not in b_gits.keys():
nodepack_diff['removed'].append(k)
elif not v['disabled'] and b_gits[k]['disabled']:
nodepack_diff['removed'].append(k)
elif v['disabled'] and not b_gits[k]['disabled']:
nodepack_diff['added'].append(k)
elif v['hash'] != b_gits[k]['hash']:
a_date = v.get('commit_timestamp')
b_date = b_gits[k].get('commit_timestamp')
if a_date is not None and b_date is not None:
if a_date < b_date:
nodepack_diff['upgraded'].append(k)
elif a_date > b_date:
nodepack_diff['downgraded'].append(k)
else:
nodepack_diff['changed'].append(k)

# check: pip packages
a_pip = a.get('pips', {})
b_pip = b.get('pips', {})
for k, v in a_pip.items():
if '==' in k:
package_name, version = k.split('==', 1)
else:
package_name, version = k, None

for k2, v2 in b_pip.items():
if '==' in k2:
package_name2, version2 = k2.split('==', 1)
else:
package_name2, version2 = k2, None

if package_name.lower() == package_name2.lower():
if version != version2:
a_ver = manager_util.StrictVersion(version) if version else None
b_ver = manager_util.StrictVersion(version2) if version2 else None
if a_ver and b_ver:
if a_ver < b_ver:
pip_diff['upgraded'][package_name] = {'from': version, 'to': version2}
elif a_ver > b_ver:
pip_diff['downgraded'][package_name] = {'from': version, 'to': version2}
elif not a_ver and b_ver:
pip_diff['added'][package_name] = version2

a_pip_names = {k.split('==', 1)[0].lower() for k in a_pip.keys()}

for k in b_pip.keys():
if '==' in k:
package_name = k.split('==', 1)[0]
package_version = k.split('==', 1)[1]
else:
package_name = k
package_version = None

if package_name.lower() not in a_pip_names:
if package_version:
pip_diff['added'][package_name] = package_version

return {'nodepack_diff': nodepack_diff, 'pip_diff': pip_diff}
4 changes: 2 additions & 2 deletions comfyui_manager/glob/manager_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2646,8 +2646,8 @@ async def get_current_snapshot(custom_nodes_only = False):
commit_hash = git_utils.get_commit_hash(fullpath)
url = git_utils.git_url(fullpath)
git_custom_nodes[url] = dict(hash=commit_hash, disabled=is_disabled)
except Exception:
print(f"Failed to extract snapshots for the custom node '{path}'.")
except Exception as e:
print(f"Failed to extract snapshots for the custom node '{path}'. / {e}")

elif path.endswith('.py'):
is_disabled = path.endswith(".py.disabled")
Expand Down
42 changes: 41 additions & 1 deletion comfyui_manager/glob/manager_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
from ..common import cm_global
from ..common import manager_downloader
from ..common import context

from ..common import snapshot_util


from ..data_models import (
Expand Down Expand Up @@ -1593,6 +1593,46 @@ async def save_snapshot(request):
return web.Response(status=400)


@routes.get("/v2/snapshot/diff")
async def get_snapshot_diff(request):
try:
from_id = request.rel_url.query.get("from")
to_id = request.rel_url.query.get("to")

if (from_id is not None and '..' in from_id) or (to_id is not None and '..' in to_id):
logging.error("/v2/snapshot/diff: invalid 'from' or 'to' parameter.")
return web.Response(status=400)

if from_id is None:
from_json = await core.get_current_snapshot()
else:
from_path = os.path.join(context.manager_snapshot_path, f"{from_id}.json")
if not os.path.exists(from_path):
logging.error(f"/v2/snapshot/diff: 'from' parameter file not found: {from_path}")
return web.Response(status=400)

from_json = snapshot_util.read_snapshot(from_path)

if to_id is None:
logging.error("/v2/snapshot/diff: 'to' parameter is required.")
return web.Response(status=401)
else:
to_path = os.path.join(context.manager_snapshot_path, f"{to_id}.json")
if not os.path.exists(to_path):
logging.error(f"/v2/snapshot/diff: 'to' parameter file not found: {to_path}")
return web.Response(status=400)

to_json = snapshot_util.read_snapshot(to_path)

return web.json_response(snapshot_util.diff_snapshot(from_json, to_json), content_type='application/json')

except Exception as e:
logging.error(f"[ComfyUI-Manager] Error in /v2/snapshot/diff: {e}")
traceback.print_exc()
# Return a generic error response
return web.Response(status=400)


def unzip_install(files):
temp_filename = "manager-temp.zip"
for url in files:
Expand Down
43 changes: 42 additions & 1 deletion comfyui_manager/legacy/manager_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from ..common import manager_downloader
from ..common import context
from ..common import manager_security
from ..common import snapshot_util


logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
Expand Down Expand Up @@ -1168,7 +1169,7 @@ async def fetch_externalmodel_list(request):
return web.json_response(json_obj, content_type='application/json')


@PromptServer.instance.routes.get("/v2/snapshot/getlist")
@routes.get("/v2/snapshot/getlist")
async def get_snapshot_list(request):
items = [f[:-5] for f in os.listdir(context.manager_snapshot_path) if f.endswith('.json')]
items.sort(reverse=True)
Expand Down Expand Up @@ -1236,6 +1237,46 @@ async def save_snapshot(request):
return web.Response(status=400)


@routes.get("/v2/snapshot/diff")
async def get_snapshot_diff(request):
try:
from_id = request.rel_url.query.get("from")
to_id = request.rel_url.query.get("to")

if (from_id is not None and '..' in from_id) or (to_id is not None and '..' in to_id):
logging.error("/v2/snapshot/diff: invalid 'from' or 'to' parameter.")
return web.Response(status=400)

if from_id is None:
from_json = await core.get_current_snapshot()
else:
from_path = os.path.join(context.manager_snapshot_path, f"{from_id}.json")
if not os.path.exists(from_path):
logging.error(f"/v2/snapshot/diff: 'from' parameter file not found: {from_path}")
return web.Response(status=400)

from_json = snapshot_util.read_snapshot(from_path)

if to_id is None:
logging.error("/v2/snapshot/diff: 'to' parameter is required.")
return web.Response(status=401)
else:
to_path = os.path.join(context.manager_snapshot_path, f"{to_id}.json")
if not os.path.exists(to_path):
logging.error(f"/v2/snapshot/diff: 'to' parameter file not found: {to_path}")
return web.Response(status=400)

to_json = snapshot_util.read_snapshot(to_path)

return web.json_response(snapshot_util.diff_snapshot(from_json, to_json), content_type='application/json')

except Exception as e:
logging.error(f"[ComfyUI-Manager] Error in /v2/snapshot/diff: {e}")
traceback.print_exc()
# Return a generic error response
return web.Response(status=400)


def unzip_install(files):
temp_filename = 'manager-temp.zip'
for url in files:
Expand Down
Loading