diff --git a/README.md b/README.md index ff1f66d5..72c6c7cb 100644 --- a/README.md +++ b/README.md @@ -272,6 +272,13 @@ NODE_CLASS_MAPPINGS.update({ * `Possible(left) + Copy(right)`: When you Double-Click on the left half of the title, it operates as `Possible Input Connections`, and when you Double-Click on the right half, it operates as `Copy All Connections`. +* Prevent downgrade of specific packages + * List the package names in the `downgrade_blacklist` section of the `config.ini` file, separating them with commas. + * e.g + ``` + downgrade_blacklist = diffusers, kornia + ``` + ## Troubleshooting * If your `git.exe` is installed in a specific location other than system git, please install ComfyUI-Manager and run ComfyUI. Then, specify the path including the file name in `git_exe = ` in the ComfyUI-Manager/config.ini file that is generated. * If updating ComfyUI-Manager itself fails, please go to the **ComfyUI-Manager** directory and execute the command `git update-ref refs/remotes/origin/main a361cc1 && git fetch --all && git pull`. diff --git a/__init__.py b/__init__.py index 4c1987d1..ddd1e618 100644 --- a/__init__.py +++ b/__init__.py @@ -17,6 +17,7 @@ import re import nodes import hashlib from datetime import datetime +from distutils.version import StrictVersion try: @@ -29,7 +30,7 @@ except: print(f"[WARN] ComfyUI-Manager: Your ComfyUI version is outdated. Please update to the latest version.") -version = [2, 11] +version = [2, 12] version_str = f"V{version[0]}.{version[1]}" + (f'.{version[2]}' if len(version) > 2 else '') print(f"### Loading: ComfyUI-Manager ({version_str})") @@ -38,19 +39,56 @@ comfy_ui_hash = "-" cache_lock = threading.Lock() +pip_map = None + + +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'([^<>!=]+)([<>!=]=?)' + pattern = r'([^<>!=]+)([<>!=]=?)(.*)' match = re.search(pattern, name) if match: name = match.group(1) if name in cm_global.pip_downgrade_blacklist: - if match is None or match.group(2) in ['<=', '==', '<']: - return True + 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 @@ -194,7 +232,8 @@ def write_config(): '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'] + '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) @@ -219,6 +258,7 @@ def read_config(): '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: @@ -235,6 +275,7 @@ def read_config(): 'double_click_policy': 'copy-all', 'windows_selector_event_loop_policy': False, 'model_download_by_agent': False, + 'downgrade_blacklist': '' } @@ -307,7 +348,6 @@ def try_install_script(url, repo_path, install_cmd): 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) @@ -831,6 +871,7 @@ def nickname_filter(json_obj): return json_obj + @server.PromptServer.instance.routes.get("/customnode/getmappings") async def fetch_customnode_mappings(request): mode = request.rel_url.query["mode"] @@ -903,6 +944,8 @@ async def update_all(request): return web.json_response(res, status=status, content_type='application/json') except: return web.Response(status=400) + finally: + clear_pip_cache() def convert_markdown_to_html(input_text): @@ -1586,6 +1629,8 @@ async def install_custom_node(request): install_cmd = [sys.executable, "-m", "pip", "install", pname] try_install_script(json_data['files'][0], ".", install_cmd) + clear_pip_cache() + if res: print(f"After restarting ComfyUI, please refresh the browser.") return web.json_response({}, content_type='application/json') @@ -1684,6 +1729,8 @@ async def update_custom_node(request): if install_type == "git-clone": res = gitclone_update(json_data['files']) + clear_pip_cache() + if res: print(f"After restarting ComfyUI, please refresh the browser.") return web.json_response({}, content_type='application/json') @@ -2132,6 +2179,7 @@ if hasattr(server.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") async def set_esheep_workflow_and_images(request): json_data = await request.json() @@ -2141,12 +2189,14 @@ async def set_esheep_workflow_and_images(request): json.dump(json_data, file, indent=4) return web.Response(status=200) + @server.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: data = json.load(file) return web.Response(status=200, text=json.dumps(data)) + def set_matrix_auth(json_data): homeserver = json_data['homeserver'] username = json_data['username'] @@ -2188,6 +2238,7 @@ def extract_model_file_names(json_data): recursive_search(json_data) return [f for f in list(file_names) if os.path.splitext(f)[1] in model_filename_extensions] + def find_file_paths(base_dir, file_names): """Find the paths of the files in the base directory.""" file_paths = {} @@ -2201,6 +2252,7 @@ def find_file_paths(base_dir, file_names): file_paths[file] = os.path.join(root, file) return file_paths + def compute_sha256_checksum(filepath): """Compute the SHA256 checksum of a file, in chunks""" sha256 = hashlib.sha256() @@ -2209,6 +2261,7 @@ def compute_sha256_checksum(filepath): sha256.update(chunk) return sha256.hexdigest() + @server.PromptServer.instance.routes.post("/manager/share") async def share_art(request): # get json data diff --git a/prestartup_script.py b/prestartup_script.py index 5b946ab9..cd32358b 100644 --- a/prestartup_script.py +++ b/prestartup_script.py @@ -7,6 +7,7 @@ import threading import re import locale import platform +from distutils.version import StrictVersion glob_path = os.path.join(os.path.dirname(__file__), "glob") @@ -292,6 +293,26 @@ else: print("** Log path: file logging is disabled") +def read_downgrade_blacklist(): + try: + import configparser + config_path = os.path.join(os.path.dirname(__file__), "config.ini") + config = configparser.ConfigParser() + config.read(config_path) + default_conf = config['default'] + + if 'downgrade_blacklist' in default_conf: + items = default_conf['downgrade_blacklist'].split(',') + items = [x.strip() for x in items if x != ''] + cm_global.pip_downgrade_blacklist += items + cm_global.pip_downgrade_blacklist = list(set(cm_global.pip_downgrade_blacklist)) + except: + pass + + +read_downgrade_blacklist() + + def check_bypass_ssl(): try: import configparser @@ -314,21 +335,30 @@ check_bypass_ssl() # Perform install processed_install = set() script_list_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "startup-scripts", "install-scripts.txt") -pip_list = None +pip_map = None def get_installed_packages(): - global pip_list + global pip_map - if pip_list is None: + if pip_map is None: try: result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True) - pip_list = set([line.split()[0].lower() for line in result.split('\n') if line.strip()]) + + 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_list + + return pip_map def is_installed(name): @@ -337,16 +367,23 @@ def is_installed(name): if name.startswith('#'): return True - pattern = r'([^<>!=]+)([<>!=]=?)' + pattern = r'([^<>!=]+)([<>!=]=?)(.*)' match = re.search(pattern, name) - + if match: name = match.group(1) if name in cm_global.pip_downgrade_blacklist: - if match is None or match.group(2) in ['<=', '==', '<']: - print(f"[ComfyUI-Manager] skip black listed pip installation: '{name}'") - return True + 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)): + print(f"[ComfyUI-Manager] skip black listed pip installation: '{name}'") + return True return name.lower() in get_installed_packages() @@ -499,7 +536,7 @@ if os.path.exists(script_list_path): print("#######################################################################\n") del processed_install -del pip_list +del pip_map def check_windows_event_loop_policy():