mirror of
https://git.datalinker.icu/ltdrdata/ComfyUI-Manager
synced 2025-12-09 06:04:31 +08:00
added: /v2/snapshot/diff
modified: use 'packaging.version` instead custom StrictVersion
This commit is contained in:
parent
5316ec1b4d
commit
62da330182
@ -15,7 +15,7 @@ import re
|
||||
import logging
|
||||
import platform
|
||||
import shlex
|
||||
|
||||
from packaging import version
|
||||
|
||||
cache_lock = threading.Lock()
|
||||
session_lock = threading.Lock()
|
||||
@ -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):
|
||||
|
||||
136
comfyui_manager/common/snapshot_util.py
Normal file
136
comfyui_manager/common/snapshot_util.py
Normal file
@ -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_version') != b.get('comfyui_version'):
|
||||
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}
|
||||
@ -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")
|
||||
|
||||
@ -47,7 +47,7 @@ from ..common import manager_util
|
||||
from ..common import cm_global
|
||||
from ..common import manager_downloader
|
||||
from ..common import context
|
||||
|
||||
from ..common import snapshot_util
|
||||
|
||||
|
||||
from ..data_models import (
|
||||
@ -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:
|
||||
|
||||
@ -24,6 +24,7 @@ from ..common import cm_global
|
||||
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})")
|
||||
@ -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)
|
||||
@ -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:
|
||||
|
||||
83
openapi.yaml
83
openapi.yaml
@ -1209,6 +1209,89 @@ paths:
|
||||
description: Snapshot saved successfully
|
||||
'400':
|
||||
description: Error saving snapshot
|
||||
/v2/snapshot/diff:
|
||||
get:
|
||||
summary: Get snapshot diff
|
||||
description: Returns the changes that would occur when restoring from the 'from' snapshot to the 'to' snapshot.
|
||||
parameters:
|
||||
- name: from
|
||||
in: query
|
||||
required: false
|
||||
description: This parameter refers to the existing snapshot; if omitted, it defaults to the current snapshot.
|
||||
schema:
|
||||
type: string
|
||||
- name: to
|
||||
in: query
|
||||
required: true
|
||||
description: This parameter is the snapshot to compare against the existing snapshot.
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
nodepack_diff:
|
||||
type: object
|
||||
properties:
|
||||
added:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
removed:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
upgraded:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
to:
|
||||
type: string
|
||||
downgraded:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
to:
|
||||
type: string
|
||||
changed:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
pip_diff:
|
||||
type: object
|
||||
properties:
|
||||
added:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
upgraded:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
to:
|
||||
type: string
|
||||
downgraded:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
properties:
|
||||
from:
|
||||
type: string
|
||||
to:
|
||||
type: string
|
||||
# ComfyUI Management Endpoints (v2)
|
||||
/v2/comfyui_manager/comfyui_versions:
|
||||
get:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user