Browse Source

feat: update, uninstall for custom nodes

pull/8/head
Dr.Lt.Data 2 years ago
parent
commit
4d54ef7738
  1. 149
      __init__.py
  2. 4
      alter-list.json
  3. 4
      custom-node-list.json
  4. 144
      js/comfyui-manager.js

149
__init__.py

@ -9,7 +9,7 @@ sys.path.append('../..')
from torchvision.datasets.utils import download_url
# ensure .js
print("### Loading: ComfyUI-Manager (V0.1)")
print("### Loading: ComfyUI-Manager (V0.2)")
comfy_path = os.path.dirname(folder_paths.__file__)
custom_nodes_path = os.path.join(comfy_path, 'custom_nodes')
@ -21,6 +21,33 @@ local_db_alter = os.path.join(comfyui_manager_path, "alter-list.json")
local_db_custom_node_list = os.path.join(comfyui_manager_path, "custom-node-list.json")
def git_repo_has_updates(path):
# Check if the path is a git repository
if not os.path.exists(os.path.join(path, '.git')):
raise ValueError('Not a git repository')
# Fetch the latest commits from the remote repository
subprocess.run(['git', 'fetch'], check=True, cwd=path)
# Get the current commit hash and the commit hash of the remote branch
commit_hash = subprocess.check_output(['git', 'rev-parse', 'HEAD'], encoding='utf-8', cwd=path).strip()
remote_commit_hash = subprocess.check_output(['git', 'rev-parse', '@{u}'], encoding='utf-8', cwd=path).strip()
# Compare the commit hashes to determine if the local repository is behind the remote repository
if commit_hash != remote_commit_hash:
return True
return False
def git_pull(path):
print(f"path: {path}")
if not os.path.exists(os.path.join(path, '.git')):
raise ValueError('Not a git repository')
subprocess.run(['git', 'pull'], check=True, cwd=path)
async def get_data(uri):
print(f"FECTH DATA from: {uri}")
if uri.startswith("http"):
@ -101,7 +128,14 @@ def check_a_custom_node_installed(item):
dir_name = os.path.splitext(os.path.basename(item['files'][0]))[0].replace(".git", "")
dir_path = os.path.join(custom_nodes_path, dir_name)
if os.path.exists(dir_path):
try:
if git_repo_has_updates(dir_path):
item['installed'] = 'Update'
else:
item['installed'] = 'True'
except:
item['installed'] = 'True'
else:
item['installed'] = 'False'
@ -208,7 +242,7 @@ def unzip_install(files):
print(f"Install(unzip) error: {url} / {e}")
return False
print("Installation successful.")
print("Installation was successful.")
return True
@ -228,7 +262,7 @@ def download_url_with_agent(url, save_path):
print(f"Download error: {url} / {e}")
return False
print("Installation successful.")
print("Installation was successful.")
return True
@ -247,7 +281,23 @@ def copy_install(files, js_path_name=None):
print(f"Install(copy) error: {url} / {e}")
return False
print("Installation successful.")
print("Installation was successful.")
return True
def copy_uninstall(files, js_path_name=None):
for url in files:
dir_name = os.path.basename(url)
base_path = custom_nodes_path if url.endswith('.py') else js_path
file_path = os.path.join(base_path, dir_name)
try:
os.remove(file_path)
except Exception as e:
print(f"UnInstall(copy) error: {url} / {e}")
return False
print("Uninstallation was successful.")
return True
@ -291,7 +341,50 @@ def gitclone_install(files):
print(f"Install(git-clone) error: {url} / {e}")
return False
print("Installation successful.")
print("Installation was successful.")
return True
def gitclone_uninstall(files):
import shutil
import os
print(f"uninstall: {files}")
for url in files:
try:
dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "")
dir_path = os.path.join(custom_nodes_path, dir_name)
# safey check
if dir_path == '/' or dir_path[1:] == ":/" or dir_path == '':
print(f"Uninstall(git-clone) error: invalid path '{dir_path}' for '{url}'")
return False
shutil.rmtree(dir_path)
except Exception as e:
print(f"Uninstall(git-clone) error: {url} / {e}")
return False
print("Uninstallation was successful.")
return True
def gitclone_update(files):
import shutil
import os
print(f"uninstall: {files}")
for url in files:
try:
dir_name = os.path.splitext(os.path.basename(url))[0].replace(".git", "")
dir_path = os.path.join(custom_nodes_path, dir_name)
git_pull(dir_path)
except Exception as e:
print(f"Update(git-clone) error: {url} / {e}")
return False
print("Uninstallation was successful.")
return True
@ -321,6 +414,48 @@ async def install_custom_node(request):
return web.Response(status=400)
@server.PromptServer.instance.routes.post("/customnode/uninstall")
async def install_custom_node(request):
json_data = await request.json()
install_type = json_data['install_type']
print(f"Uninstall custom node '{json_data['title']}'")
res = False
if install_type == "copy":
js_path_name = json_data['js_path'] if 'js_path' in json_data else None
res = copy_uninstall(json_data['files'], js_path_name)
elif install_type == "git-clone":
res = gitclone_uninstall(json_data['files'])
if res:
return web.json_response({}, content_type='application/json')
return web.Response(status=400)
@server.PromptServer.instance.routes.post("/customnode/update")
async def install_custom_node(request):
json_data = await request.json()
install_type = json_data['install_type']
print(f"Update custom node '{json_data['title']}'")
res = False
if install_type == "git-clone":
res = gitclone_update(json_data['files'])
if res:
return web.json_response({}, content_type='application/json')
return web.Response(status=400)
@server.PromptServer.instance.routes.post("/model/install")
async def install_model(request):
json_data = await request.json()
@ -339,3 +474,7 @@ async def install_model(request):
return web.json_response({}, content_type='application/json')
return web.Response(status=400)
NODE_CLASS_MAPPINGS = {}
__all__ = ['NODE_CLASS_MAPPINGS']

4
alter-list.json

@ -12,8 +12,8 @@
},
{
"id":"https://github.com/ltdrdata/ComfyUI-Impact-Pack",
"tags":"ddetailer, adetailer, ddsd, DD",
"description": "To implement the feature of automatically detecting faces and enhancing details, various detection nodes and detailers provided by the Impact Pack can be applied."
"tags":"ddetailer, adetailer, ddsd, DD, loopback scaler",
"description": "To implement the feature of automatically detecting faces and enhancing details, various detection nodes and detailers provided by the Impact Pack can be applied. Similarly to Loopback Scaler, it also provides various custom workflows that can apply Ksampler while gradually scaling up."
},
{
"id":"https://github.com/biegert/ComfyUI-CLIPSeg/raw/main/custom_nodes/clipseg.py",

4
custom-node-list.json

@ -8,7 +8,7 @@
"https://github.com/ltdrdata/ComfyUI-Impact-Pack"
],
"install_type": "git-clone",
"description": "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details."
"description": "This extension offers various detector nodes and detailer nodes that allow you to configure a workflow that automatically enhances facial details. And provide iterative upscaler."
},
{
"author": "comfyanonymous",
@ -82,7 +82,7 @@
},
{
"author": "BlenderNeko",
"title": "(WIP) Tiled sampling for ComfyUI",
"title": "Tiled sampling for ComfyUI",
"reference": "https://github.com/BlenderNeko/ComfyUI_TiledKSampler",
"files": [
"https://github.com/BlenderNeko/ComfyUI_TiledKSampler"

144
js/comfyui-manager.js vendored

@ -35,24 +35,30 @@ async function getModelList() {
return data;
}
async function install_custom_node(target, caller) {
async function install_custom_node(target, caller, mode) {
if(caller) {
caller.startInstall(target);
try {
const response = await fetch('/customnode/install', {
const response = await fetch(`/customnode/${mode}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(target)
});
if(response.status == 400) {
app.ui.dialog.show(`${mode} failed: ${target.title}`);
app.ui.dialog.element.style.zIndex = 9999;
return false;
}
const status = await response.json();
app.ui.dialog.close();
target.installed = 'True';
return true;
}
catch(exception) {
app.ui.dialog.show(`Install failed: ${target.title} / ${exception}`);
app.ui.dialog.show(`${mode} failed: ${target.title} / ${exception}`);
app.ui.dialog.element.style.zIndex = 9999;
return false;
}
@ -112,18 +118,37 @@ class CustomNodesInstaller extends ComfyDialog {
}
startInstall(target) {
this.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
const self = CustomNodesInstaller.instance;
self.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
for(let i in this.install_buttons) {
this.install_buttons[i].disabled = true;
this.install_buttons[i].style.backgroundColor = 'gray';
for(let i in self.install_buttons) {
self.install_buttons[i].disabled = true;
self.install_buttons[i].style.backgroundColor = 'gray';
}
}
async invalidateControl() {
this.clear();
// splash
while (this.element.children.length) {
this.element.removeChild(this.element.children[0]);
}
const msg = $el('div', {id:'custom-message'},
[$el('br'),
'The custom node DB is currently being updated, and updates to custom nodes are being checked for.',
$el('br')]);
msg.style.height = '100px';
msg.style.verticalAlign = 'middle';
this.element.appendChild(msg);
// invalidate
this.data = (await getCustomNodes()).custom_nodes;
this.element.removeChild(msg);
while (this.element.children.length) {
this.element.removeChild(this.element.children[0]);
}
@ -190,14 +215,23 @@ class CustomNodesInstaller extends ComfyDialog {
data5.style.textAlign = "center";
var installBtn = document.createElement('button');
var installBtn2 = null;
this.install_buttons.push(installBtn);
switch(data.installed) {
case 'Update':
installBtn2 = document.createElement('button');
installBtn2.innerHTML = 'Update';
installBtn2.style.backgroundColor = 'blue';
this.install_buttons.push(installBtn2);
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
break;
case 'True':
installBtn.innerHTML = 'Installed';
installBtn.style.backgroundColor = 'green';
installBtn.disabled = true;
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
break;
case 'False':
installBtn.innerHTML = 'Install';
@ -205,11 +239,26 @@ class CustomNodesInstaller extends ComfyDialog {
break;
default:
installBtn.innerHTML = 'Try Install';
installBtn.style.backgroundColor = 'brown';
installBtn.style.backgroundColor = 'silver';
}
if(installBtn2 != null) {
installBtn2.addEventListener('click', function() {
install_custom_node(data, CustomNodesInstaller.instance, 'update');
});
data5.appendChild(installBtn2);
}
installBtn.addEventListener('click', function() {
install_custom_node(data, CustomNodesInstaller.instance);
if(this.innerHTML == 'Uninstall') {
if (confirm(`Are you sure uninstall ${data.title}?`)) {
install_custom_node(data, CustomNodesInstaller.instance, 'uninstall');
}
}
else {
install_custom_node(data, CustomNodesInstaller.instance, 'install');
}
});
data5.appendChild(installBtn);
@ -281,18 +330,37 @@ class AlternativesInstaller extends ComfyDialog {
}
startInstall(target) {
this.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
const self = AlternativesInstaller.instance;
self.updateMessage(`<BR><font color="green">Installing '${target.title}'</font>`);
for(let i in this.install_buttons) {
this.install_buttons[i].disabled = true;
this.install_buttons[i].style.backgroundColor = 'gray';
for(let i in self.install_buttons) {
self.install_buttons[i].disabled = true;
self.install_buttons[i].style.backgroundColor = 'gray';
}
}
async invalidateControl() {
this.clear();
// splash
while (this.element.children.length) {
this.element.removeChild(this.element.children[0]);
}
const msg = $el('div', {id:'custom-message'},
[$el('br'),
'The custom node DB is currently being updated, and updates to custom nodes are being checked for.',
$el('br')]);
msg.style.height = '100px';
msg.style.verticalAlign = 'middle';
this.element.appendChild(msg);
// invalidate
this.data = (await getAlterList()).items;
this.element.removeChild(msg);
while (this.element.children.length) {
this.element.removeChild(this.element.children[0]);
}
@ -372,14 +440,23 @@ class AlternativesInstaller extends ComfyDialog {
if(data.custom_node) {
var installBtn = document.createElement('button');
var installBtn2 = null;
this.install_buttons.push(installBtn);
switch(data.custom_node.installed) {
case 'Update':
installBtn2 = document.createElement('button');
installBtn2.innerHTML = 'Update';
installBtn2.style.backgroundColor = 'blue';
this.install_buttons.push(installBtn2);
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
break;
case 'True':
installBtn.innerHTML = 'Installed';
installBtn.style.backgroundColor = 'green';
installBtn.disabled = true;
installBtn.innerHTML = 'Uninstall';
installBtn.style.backgroundColor = 'red';
break;
case 'False':
installBtn.innerHTML = 'Install';
@ -387,11 +464,26 @@ class AlternativesInstaller extends ComfyDialog {
break;
default:
installBtn.innerHTML = 'Try Install';
installBtn.style.backgroundColor = 'brown';
installBtn.style.backgroundColor = 'silver';
}
if(installBtn2 != null) {
installBtn2.addEventListener('click', function() {
install_custom_node(data.custom_node, AlternativesInstaller.instance, 'update');
});
data6.appendChild(installBtn2);
}
installBtn.addEventListener('click', function() {
install_custom_node(data.custom_node, AlternativesInstaller.instance);
if(this.innerHTML == 'Uninstall') {
if (confirm(`Are you sure uninstall ${data.title}?`)) {
install_custom_node(data.custom_node, AlternativesInstaller.instance, 'uninstall');
}
}
else {
install_custom_node(data.custom_node, AlternativesInstaller.instance, 'install');
}
});
data6.appendChild(installBtn);
@ -476,11 +568,13 @@ class ModelInstaller extends ComfyDialog {
}
startInstall(target) {
this.updateMessage(`<BR><font color="green">Installing '${target.name}'</font>`);
const self = ModelInstaller.instance;
self.updateMessage(`<BR><font color="green">Installing '${target.name}'</font>`);
for(let i in this.install_buttons) {
this.install_buttons[i].disabled = true;
this.install_buttons[i].style.backgroundColor = 'gray';
for(let i in self.install_buttons) {
self.install_buttons[i].disabled = true;
self.install_buttons[i].style.backgroundColor = 'gray';
}
}

Loading…
Cancel
Save