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 logging
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
import shlex
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
cache_lock = threading.Lock()
|
cache_lock = threading.Lock()
|
||||||
session_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.")
|
# print(f"[ComfyUI-Manager] 'distutils' package not found. Activating fallback mode for compatibility.")
|
||||||
class StrictVersion:
|
class StrictVersion:
|
||||||
def __init__(self, version_string):
|
def __init__(self, version_string):
|
||||||
|
self.obj = version.parse(version_string)
|
||||||
self.version_string = version_string
|
self.version_string = version_string
|
||||||
self.major = 0
|
self.major = self.obj.major
|
||||||
self.minor = 0
|
self.minor = self.obj.minor
|
||||||
self.patch = 0
|
self.patch = self.obj.micro
|
||||||
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]
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
version = f"{self.major}.{self.minor}.{self.patch}"
|
return self.version_string
|
||||||
if self.pre_release:
|
|
||||||
version += f"-{self.pre_release}"
|
|
||||||
return version
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return (self.major, self.minor, self.patch, self.pre_release) == \
|
return self.obj == other.obj
|
||||||
(other.major, other.minor, other.patch, other.pre_release)
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
if (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch):
|
return self.obj < other.obj
|
||||||
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
|
|
||||||
|
|
||||||
def __le__(self, other):
|
def __le__(self, other):
|
||||||
return self == other or self < other
|
return self.obj == other.obj or self.obj < other.obj
|
||||||
|
|
||||||
def __gt__(self, other):
|
def __gt__(self, other):
|
||||||
return not self <= other
|
return not self.obj <= other.obj
|
||||||
|
|
||||||
def __ge__(self, other):
|
def __ge__(self, other):
|
||||||
return not self < other
|
return not self.obj < other.obj
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self == other
|
return not self.obj == other.obj
|
||||||
|
|
||||||
|
|
||||||
def simple_hash(input_string):
|
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)
|
commit_hash = git_utils.get_commit_hash(fullpath)
|
||||||
url = git_utils.git_url(fullpath)
|
url = git_utils.git_url(fullpath)
|
||||||
git_custom_nodes[url] = dict(hash=commit_hash, disabled=is_disabled)
|
git_custom_nodes[url] = dict(hash=commit_hash, disabled=is_disabled)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
print(f"Failed to extract snapshots for the custom node '{path}'.")
|
print(f"Failed to extract snapshots for the custom node '{path}'. / {e}")
|
||||||
|
|
||||||
elif path.endswith('.py'):
|
elif path.endswith('.py'):
|
||||||
is_disabled = path.endswith(".py.disabled")
|
is_disabled = path.endswith(".py.disabled")
|
||||||
|
|||||||
@ -47,7 +47,7 @@ from ..common import manager_util
|
|||||||
from ..common import cm_global
|
from ..common import cm_global
|
||||||
from ..common import manager_downloader
|
from ..common import manager_downloader
|
||||||
from ..common import context
|
from ..common import context
|
||||||
|
from ..common import snapshot_util
|
||||||
|
|
||||||
|
|
||||||
from ..data_models import (
|
from ..data_models import (
|
||||||
@ -1593,6 +1593,46 @@ async def save_snapshot(request):
|
|||||||
return web.Response(status=400)
|
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):
|
def unzip_install(files):
|
||||||
temp_filename = "manager-temp.zip"
|
temp_filename = "manager-temp.zip"
|
||||||
for url in files:
|
for url in files:
|
||||||
|
|||||||
@ -24,6 +24,7 @@ from ..common import cm_global
|
|||||||
from ..common import manager_downloader
|
from ..common import manager_downloader
|
||||||
from ..common import context
|
from ..common import context
|
||||||
from ..common import manager_security
|
from ..common import manager_security
|
||||||
|
from ..common import snapshot_util
|
||||||
|
|
||||||
|
|
||||||
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
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')
|
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):
|
async def get_snapshot_list(request):
|
||||||
items = [f[:-5] for f in os.listdir(context.manager_snapshot_path) if f.endswith('.json')]
|
items = [f[:-5] for f in os.listdir(context.manager_snapshot_path) if f.endswith('.json')]
|
||||||
items.sort(reverse=True)
|
items.sort(reverse=True)
|
||||||
@ -1236,6 +1237,46 @@ async def save_snapshot(request):
|
|||||||
return web.Response(status=400)
|
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):
|
def unzip_install(files):
|
||||||
temp_filename = 'manager-temp.zip'
|
temp_filename = 'manager-temp.zip'
|
||||||
for url in files:
|
for url in files:
|
||||||
|
|||||||
83
openapi.yaml
83
openapi.yaml
@ -1209,6 +1209,89 @@ paths:
|
|||||||
description: Snapshot saved successfully
|
description: Snapshot saved successfully
|
||||||
'400':
|
'400':
|
||||||
description: Error saving snapshot
|
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)
|
# ComfyUI Management Endpoints (v2)
|
||||||
/v2/comfyui_manager/comfyui_versions:
|
/v2/comfyui_manager/comfyui_versions:
|
||||||
get:
|
get:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user