mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-15 17:30:13 +08:00
perf: 优化插件代码结构。
fix: 修复卸载插件之后,线程无限自旋的问题。
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,3 +4,5 @@ botpy.log
|
||||
data.db
|
||||
configs/session
|
||||
configs/config.yaml
|
||||
**/.DS_Store
|
||||
temp
|
||||
|
||||
@@ -155,6 +155,14 @@ class AstrBotDashBoard():
|
||||
message="",
|
||||
data=_plugin_resp
|
||||
).__dict__
|
||||
|
||||
@self.dashboard_be.post("/api/extensions/install")
|
||||
def install_plugin():
|
||||
pass
|
||||
|
||||
@self.dashboard_be.post("/api/extensions/uninstall")
|
||||
def uninstall_plugin():
|
||||
pass
|
||||
|
||||
def register(self, name: str):
|
||||
def decorator(func):
|
||||
@@ -163,7 +171,9 @@ class AstrBotDashBoard():
|
||||
return decorator
|
||||
|
||||
def run(self):
|
||||
gu.log(f"\n\n==================\n您可以访问:\n\thttp://localhost:6185/\n来登录可视化面板。\n==================\n\n", tag="可视化面板")
|
||||
ip_address = gu.get_local_ip_addresses()
|
||||
ip_str = f"http://{ip_address}:6185\n\thttp://localhost:6185"
|
||||
gu.log(f"\n\n==================\n您可以访问:\n\n\t{ip_str}\n\n来登录可视化面板。\n注意: 所有配置项现已全量迁移至 cmd_config.json 文件下。您可以登录可视化面板在线修改配置。\n==================\n\n", tag="可视化面板")
|
||||
# self.dashboard_be.run(host="0.0.0.0", port=6185)
|
||||
http_server = make_server('0.0.0.0', 6185, self.dashboard_be)
|
||||
http_server.serve_forever()
|
||||
|
||||
159
app.py
159
app.py
@@ -1,159 +0,0 @@
|
||||
# fastapi
|
||||
from typing import Union
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
|
||||
import pymysql
|
||||
import re
|
||||
import requests
|
||||
from pydantic import BaseModel
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
db = pymysql.connect(
|
||||
host='localhost',
|
||||
port=3306,
|
||||
user='sorbot',
|
||||
password='sorbot123.,.',
|
||||
database='sorbot'
|
||||
)
|
||||
|
||||
cnt_10m = 0
|
||||
cnt_ips_10m = 0
|
||||
url = 'http://localhost:9091/metrics/job/sorbot'
|
||||
ip_ls = []
|
||||
|
||||
class Tick(BaseModel):
|
||||
version: str
|
||||
count: int
|
||||
ip: str
|
||||
others: str
|
||||
|
||||
def get_from_db(ip: str):
|
||||
db.ping()
|
||||
cursor = db.cursor()
|
||||
sql = f"SELECT * FROM bases WHERE ip = '{ip}'"
|
||||
print(sql)
|
||||
cursor.execute(sql)
|
||||
result = cursor.fetchone()
|
||||
cursor.close()
|
||||
return result
|
||||
|
||||
# 当前 IP:117.133.151.135 来自于:中国 北京 北京 移动
|
||||
# def filter_ip(ori: str):
|
||||
# # 正则表达式得到ip
|
||||
# ip = re.findall(r'\b(?:\d{1,3}\.){3}\d{1,3}\b|\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b', ori)[0]
|
||||
# return ip
|
||||
|
||||
def thread_total_cnt():
|
||||
global cnt_10m, cnt_ips_10m
|
||||
while True:
|
||||
try:
|
||||
metric_data = 'qqc_total ' + str(cnt_10m)
|
||||
cmd = "echo " + metric_data + " | curl --data-binary @- " + url
|
||||
print(cmd)
|
||||
res = os.system('echo \'' + metric_data + '\' | curl --data-binary @- ' + url)
|
||||
cnt_10m = 0
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
try:
|
||||
metric_data = 'qqc_ips_total ' + str(cnt_ips_10m)
|
||||
cmd = "echo " + metric_data + " | curl --data-binary @- " + url
|
||||
print(cmd)
|
||||
res = os.system('echo \'' + metric_data + '\' | curl --data-binary @- ' + url)
|
||||
cnt_ips_10m = 0
|
||||
ip_ls.clear()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print('thread_total_cnt sleep 5 min')
|
||||
time.sleep(610)
|
||||
|
||||
|
||||
# 启动线程
|
||||
t = threading.Thread(target=thread_total_cnt, daemon=True)
|
||||
t.start()
|
||||
|
||||
|
||||
|
||||
def push_to_gateway(tick: Tick):
|
||||
# push to gateway
|
||||
ip = tick.ip
|
||||
others = json.loads(tick.others)
|
||||
admin = others['admin']
|
||||
if admin is None:
|
||||
admin = "unknown"
|
||||
if tick.version is None:
|
||||
tick.version = "unknown"
|
||||
metric_data = 'qqc_ips{version="' + tick.version + '",ip="' + ip + '",admin="' + admin + '"} ' + str(tick.count)
|
||||
print(metric_data)
|
||||
# r = requests.post(url, data=metric_data.encode('utf-8'), )
|
||||
# print(r.text)
|
||||
cmd = "echo " + metric_data + " | curl --data-binary @- " + url
|
||||
print(cmd)
|
||||
res = os.system('echo \'' + metric_data + '\' | curl --data-binary @- ' + url)
|
||||
|
||||
|
||||
|
||||
@app.post("/upload")
|
||||
def upload(tick: Tick, request: Request):
|
||||
global cnt_ips_10m, cnt_10m
|
||||
# ip = filter_ip(tick.ip)
|
||||
ip = request.client.host
|
||||
print("IP", ip, "|", "TICK", tick.__dict__)
|
||||
tick.ip = ip
|
||||
result = get_from_db(ip)
|
||||
try:
|
||||
if result is None:
|
||||
# 插入数据库
|
||||
db.ping()
|
||||
cursor = db.cursor()
|
||||
sql = f"INSERT INTO bases (ip, count, version, others) VALUES ('{ip}', {tick.count}, '{tick.version}', '{tick.others}')"
|
||||
cursor.execute(sql)
|
||||
db.commit()
|
||||
cursor.close()
|
||||
else:
|
||||
# 更新数据库
|
||||
db.ping()
|
||||
cursor = db.cursor()
|
||||
cnt = result[1] + tick.count
|
||||
sql = f"UPDATE bases SET count = {cnt}, version = '{tick.version}', others = '{tick.others}' WHERE ip = '{ip}'"
|
||||
cursor.execute(sql)
|
||||
db.commit()
|
||||
cursor.close()
|
||||
# print(result)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
raise e
|
||||
try:
|
||||
# insert 到 clicks 表。结构是:total_cnt, ip, timestamp, version
|
||||
db.ping()
|
||||
cursor = db.cursor()
|
||||
sql = f"INSERT INTO clicks (total_cnt, ip, timestamp, version) VALUES ({tick.count}, '{ip}', {int(time.time())}, '{tick.version}')"
|
||||
cursor.execute(sql)
|
||||
db.commit()
|
||||
cursor.close()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
raise e
|
||||
cnt_10m += tick.count
|
||||
if ip not in ip_ls:
|
||||
cnt_ips_10m += 1
|
||||
ip_ls.append(ip)
|
||||
|
||||
# push to gateway
|
||||
push_to_gateway(tick)
|
||||
|
||||
return {
|
||||
"code": 200,
|
||||
"status": "ok",
|
||||
}
|
||||
|
||||
|
||||
@app.get("/items/{item_id}")
|
||||
def read_item(item_id: int, q: Union[str, None] = None):
|
||||
return {"item_id": item_id, "q": q}
|
||||
@@ -33,7 +33,8 @@ from model.provider.provider import Provider
|
||||
from model.command.command import Command
|
||||
from util import general_utils as gu
|
||||
from util.cmd_config import CmdConfig as cc
|
||||
import util.gplugin as gplugin
|
||||
import util.function_calling.gplugin as gplugin
|
||||
import util.plugin_util as putil
|
||||
from PIL import Image as PILImage
|
||||
import io
|
||||
import traceback
|
||||
@@ -357,7 +358,7 @@ def initBot(cfg):
|
||||
gu.log("--------加载插件--------", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow'])
|
||||
# 加载插件
|
||||
_command = Command(None, _global_object)
|
||||
ok, err = _command.plugin_reload(_global_object.cached_plugins)
|
||||
ok, err = putil.plugin_reload(_global_object.cached_plugins)
|
||||
if ok:
|
||||
gu.log("加载插件完成", gu.LEVEL_INFO)
|
||||
else:
|
||||
|
||||
@@ -118,46 +118,6 @@ class Command:
|
||||
return None
|
||||
except BaseException as e:
|
||||
raise e
|
||||
|
||||
def plugin_reload(self, cached_plugins: dict, target: str = None, all: bool = False):
|
||||
plugins = self.get_plugin_modules()
|
||||
if plugins is None:
|
||||
return False, "未找到任何插件模块"
|
||||
fail_rec = ""
|
||||
for plugin in plugins:
|
||||
try:
|
||||
p = plugin['module']
|
||||
root_dir_name = plugin['pname']
|
||||
if p not in cached_plugins or p == target or all:
|
||||
module = __import__("addons.plugins." + root_dir_name + "." + p, fromlist=[p])
|
||||
if p in cached_plugins:
|
||||
module = importlib.reload(module)
|
||||
cls = putil.get_classes(p, module)
|
||||
obj = getattr(module, cls[0])()
|
||||
try:
|
||||
info = obj.info()
|
||||
if 'name' not in info or 'desc' not in info or 'version' not in info or 'author' not in info:
|
||||
fail_rec += f"载入插件{p}失败,原因: 插件信息不完整\n"
|
||||
continue
|
||||
if isinstance(info, dict) == False:
|
||||
fail_rec += f"载入插件{p}失败,原因: 插件信息格式不正确\n"
|
||||
continue
|
||||
except BaseException as e:
|
||||
fail_rec += f"调用插件{p} info失败, 原因: {str(e)}\n"
|
||||
continue
|
||||
cached_plugins[info['name']] = {
|
||||
"module": module,
|
||||
"clsobj": obj,
|
||||
"info": info,
|
||||
"name": info['name'],
|
||||
"root_dir_name": root_dir_name,
|
||||
}
|
||||
except BaseException as e:
|
||||
fail_rec += f"加载{p}插件出现问题,原因 {str(e)}\n"
|
||||
if fail_rec == "":
|
||||
return True, None
|
||||
else:
|
||||
return False, fail_rec
|
||||
|
||||
'''
|
||||
插件指令
|
||||
@@ -170,81 +130,28 @@ class Command:
|
||||
p = gu.create_text_image("【插件指令面板】", "安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin d 插件名 \n重载插件: \nplugin reload\n查看插件列表:\nplugin l\n更新插件: plugin u 插件名\n")
|
||||
return True, [Image.fromFileSystem(p)], "plugin"
|
||||
else:
|
||||
ppath = ""
|
||||
if os.path.exists("addons/plugins"):
|
||||
ppath = "addons/plugins"
|
||||
elif os.path.exists("QQChannelChatGPT/addons/plugins"):
|
||||
ppath = "QQChannelChatGPT/addons/plugins"
|
||||
else:
|
||||
return False, "未找到插件目录", "plugin"
|
||||
if l[1] == "i":
|
||||
if role != "admin":
|
||||
return False, f"你的身份组{role}没有权限安装插件", "plugin"
|
||||
try:
|
||||
# 删除末尾的/
|
||||
if l[2].endswith("/"):
|
||||
l[2] = l[2][:-1]
|
||||
# 得到url的最后一段
|
||||
d = l[2].split("/")[-1]
|
||||
# 转换非法字符:-
|
||||
d = d.replace("-", "_")
|
||||
# 创建文件夹
|
||||
plugin_path = os.path.join(ppath, d)
|
||||
if os.path.exists(plugin_path):
|
||||
shutil.rmtree(plugin_path)
|
||||
os.mkdir(plugin_path)
|
||||
Repo.clone_from(l[2],to_path=plugin_path,branch='master')
|
||||
|
||||
# 读取插件的requirements.txt
|
||||
if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
|
||||
mm = pipmain(['install', '-r', os.path.join(plugin_path, "requirements.txt")])
|
||||
if mm != 0:
|
||||
return False, "插件依赖安装失败,需要您手动pip安装对应插件的依赖。", "plugin"
|
||||
# 加载没缓存的插件
|
||||
ok, err = self.plugin_reload(cached_plugins, target=d)
|
||||
if ok:
|
||||
return True, "插件拉取并载入成功~", "plugin"
|
||||
else:
|
||||
# if os.path.exists(plugin_path):
|
||||
# shutil.rmtree(plugin_path)
|
||||
return False, f"插件拉取载入失败。\n跟踪: \n{err}", "plugin"
|
||||
putil.install_plugin(l[2], cached_plugins)
|
||||
return True, "插件拉取并载入成功~", "plugin"
|
||||
except BaseException as e:
|
||||
return False, f"拉取插件失败,原因: {str(e)}", "plugin"
|
||||
elif l[1] == "d":
|
||||
if role != "admin":
|
||||
return False, f"你的身份组{role}没有权限删除插件", "plugin"
|
||||
if l[2] not in cached_plugins:
|
||||
return False, "未找到该插件", "plugin"
|
||||
|
||||
try:
|
||||
root_dir_name = cached_plugins[l[2]]["root_dir_name"]
|
||||
self.remove_dir(os.path.join(ppath, root_dir_name))
|
||||
del cached_plugins[l[2]]
|
||||
putil.uninstall_plugin(l[2], cached_plugins)
|
||||
return True, "插件卸载成功~", "plugin"
|
||||
except BaseException as e:
|
||||
return False, f"卸载插件失败,原因: {str(e)}", "plugin"
|
||||
elif l[1] == "u":
|
||||
if l[2] not in cached_plugins:
|
||||
return False, "未找到该插件", "plugin"
|
||||
root_dir_name = cached_plugins[l[2]]["root_dir_name"]
|
||||
plugin_path = os.path.join(ppath, root_dir_name)
|
||||
try:
|
||||
repo = Repo(path = plugin_path)
|
||||
repo.remotes.origin.pull()
|
||||
# 读取插件的requirements.txt
|
||||
if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
|
||||
mm = pipmain(['install', '-r', os.path.join(plugin_path, "requirements.txt")])
|
||||
if mm != 0:
|
||||
return False, "插件依赖安装失败,需要您手动pip安装对应插件的依赖。", "plugin"
|
||||
|
||||
ok, err = self.plugin_reload(cached_plugins, target=l[2])
|
||||
if ok:
|
||||
return True, "\n更新插件成功!!", "plugin"
|
||||
else:
|
||||
return False, "更新插件成功,但是重载插件失败。\n问题跟踪: \n"+err, "plugin"
|
||||
putil.update_plugin(l[2], cached_plugins)
|
||||
return True, "\n更新插件成功!!", "plugin"
|
||||
except BaseException as e:
|
||||
return False, "更新插件失败, 请使用plugin i指令覆盖安装", "plugin"
|
||||
|
||||
return False, f"更新插件失败,原因: {str(e)}。\n建议: 使用 plugin i 指令进行覆盖安装(插件数据可能会丢失)", "plugin"
|
||||
elif l[1] == "l":
|
||||
try:
|
||||
plugin_list_info = "\n".join([f"{k}: \n名称: {v['info']['name']}\n简介: {v['info']['desc']}\n版本: {v['info']['version']}\n作者: {v['info']['author']}\n" for k, v in cached_plugins.items()])
|
||||
@@ -262,45 +169,34 @@ class Command:
|
||||
return False, "未找到该插件", "plugin"
|
||||
except BaseException as e:
|
||||
return False, f"获取插件信息失败,原因: {str(e)}", "plugin"
|
||||
elif l[1] == "reload":
|
||||
if role != "admin":
|
||||
return False, f"你的身份组{role}没有权限重载插件", "plugin"
|
||||
for plugin in cached_plugins:
|
||||
try:
|
||||
print(f"更新插件 {plugin} 依赖...")
|
||||
plugin_path = os.path.join(ppath, cached_plugins[plugin]["root_dir_name"])
|
||||
if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
|
||||
mm = pipmain(['install', '-r', os.path.join(plugin_path, "requirements.txt"), "--quiet"])
|
||||
if mm != 0:
|
||||
return False, "插件依赖安装失败,需要您手动pip安装对应插件的依赖。", "plugin"
|
||||
except BaseException as e:
|
||||
print(f"插件{plugin}依赖安装失败,原因: {str(e)}")
|
||||
try:
|
||||
ok, err = self.plugin_reload(cached_plugins, all = True)
|
||||
if ok:
|
||||
return True, "\n重载插件成功~", "plugin"
|
||||
else:
|
||||
# if os.path.exists(plugin_path):
|
||||
# shutil.rmtree(plugin_path)
|
||||
return False, f"插件重载失败。\n跟踪: \n{err}", "plugin"
|
||||
except BaseException as e:
|
||||
return False, f"插件重载失败,原因: {str(e)}", "plugin"
|
||||
|
||||
# elif l[1] == "reload":
|
||||
# if role != "admin":
|
||||
# return False, f"你的身份组{role}没有权限重载插件", "plugin"
|
||||
# for plugin in cached_plugins:
|
||||
# try:
|
||||
# print(f"更新插件 {plugin} 依赖...")
|
||||
# plugin_path = os.path.join(ppath, cached_plugins[plugin]["root_dir_name"])
|
||||
# if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
|
||||
# mm = pipmain(['install', '-r', os.path.join(plugin_path, "requirements.txt"), "--quiet"])
|
||||
# if mm != 0:
|
||||
# return False, "插件依赖安装失败,需要您手动pip安装对应插件的依赖。", "plugin"
|
||||
# except BaseException as e:
|
||||
# print(f"插件{plugin}依赖安装失败,原因: {str(e)}")
|
||||
# try:
|
||||
# ok, err = self.plugin_reload(cached_plugins, all = True)
|
||||
# if ok:
|
||||
# return True, "\n重载插件成功~", "plugin"
|
||||
# else:
|
||||
# # if os.path.exists(plugin_path):
|
||||
# # shutil.rmtree(plugin_path)
|
||||
# return False, f"插件重载失败。\n跟踪: \n{err}", "plugin"
|
||||
# except BaseException as e:
|
||||
# return False, f"插件重载失败,原因: {str(e)}", "plugin"
|
||||
|
||||
elif l[1] == "dev":
|
||||
if role != "admin":
|
||||
return False, f"你的身份组{role}没有权限开发者模式", "plugin"
|
||||
return True, "cached_plugins: \n" + str(cached_plugins), "plugin"
|
||||
|
||||
def remove_dir(self, file_path):
|
||||
while 1:
|
||||
if not os.path.exists(file_path):
|
||||
break
|
||||
try:
|
||||
shutil.rmtree(file_path)
|
||||
except PermissionError as e:
|
||||
err_file_path = str(e).split("\'", 2)[1]
|
||||
if os.path.exists(err_file_path):
|
||||
os.chmod(err_file_path, stat.S_IWUSR)
|
||||
|
||||
'''
|
||||
nick: 存储机器人的昵称
|
||||
|
||||
@@ -2,7 +2,7 @@ import requests
|
||||
import util.general_utils as gu
|
||||
from bs4 import BeautifulSoup
|
||||
import time
|
||||
from util.func_call import (
|
||||
from util.function_calling.func_call import (
|
||||
FuncCall,
|
||||
FuncCallJsonFormatError,
|
||||
FuncNotFoundError
|
||||
@@ -6,6 +6,7 @@ import os
|
||||
import re
|
||||
import requests
|
||||
from util.cmd_config import CmdConfig
|
||||
import socket
|
||||
|
||||
PLATFORM_GOCQ = 'gocq'
|
||||
PLATFORM_QQCHAN = 'qqchan'
|
||||
@@ -520,4 +521,14 @@ def try_migrate_config(old_config: dict):
|
||||
if cc.get("qqbot", None) is None:
|
||||
# 未迁移过
|
||||
for k in old_config:
|
||||
cc.put(k, old_config[k])
|
||||
cc.put(k, old_config[k])
|
||||
|
||||
def get_local_ip_addresses():
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(('8.8.8.8', 80))
|
||||
ip = s.getsockname()[0]
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
return ip
|
||||
@@ -3,6 +3,16 @@
|
||||
'''
|
||||
import os
|
||||
import inspect
|
||||
try:
|
||||
import git.exc
|
||||
from git.repo import Repo
|
||||
except ImportError:
|
||||
pass
|
||||
import shutil
|
||||
from pip._internal import main as pipmain
|
||||
import importlib
|
||||
import stat
|
||||
|
||||
|
||||
# 找出模块里所有的类名
|
||||
def get_classes(p_name, arg):
|
||||
@@ -31,4 +41,112 @@ def get_modules(path):
|
||||
"module": file[:-3],
|
||||
})
|
||||
return modules
|
||||
|
||||
|
||||
def get_plugin_store_path():
|
||||
if os.path.exists("addons/plugins"):
|
||||
return "addons/plugins"
|
||||
elif os.path.exists("QQChannelChatGPT/addons/plugins"):
|
||||
return "QQChannelChatGPT/addons/plugins"
|
||||
elif os.path.exists("AstrBot/addons/plugins"):
|
||||
return "AstrBot/addons/plugins"
|
||||
else:
|
||||
raise FileNotFoundError("插件文件夹不存在。")
|
||||
|
||||
def plugin_reload(self, cached_plugins: dict, target: str = None, all: bool = False):
|
||||
plugins = self.get_plugin_modules()
|
||||
if plugins is None:
|
||||
return False, "未找到任何插件模块"
|
||||
fail_rec = ""
|
||||
for plugin in plugins:
|
||||
try:
|
||||
p = plugin['module']
|
||||
root_dir_name = plugin['pname']
|
||||
if p not in cached_plugins or p == target or all:
|
||||
module = __import__("addons.plugins." + root_dir_name + "." + p, fromlist=[p])
|
||||
if p in cached_plugins:
|
||||
module = importlib.reload(module)
|
||||
cls = get_classes(p, module)
|
||||
obj = getattr(module, cls[0])()
|
||||
try:
|
||||
info = obj.info()
|
||||
if 'name' not in info or 'desc' not in info or 'version' not in info or 'author' not in info:
|
||||
fail_rec += f"载入插件{p}失败,原因: 插件信息不完整\n"
|
||||
continue
|
||||
if isinstance(info, dict) == False:
|
||||
fail_rec += f"载入插件{p}失败,原因: 插件信息格式不正确\n"
|
||||
continue
|
||||
except BaseException as e:
|
||||
fail_rec += f"调用插件{p} info失败, 原因: {str(e)}\n"
|
||||
continue
|
||||
cached_plugins[info['name']] = {
|
||||
"module": module,
|
||||
"clsobj": obj,
|
||||
"info": info,
|
||||
"name": info['name'],
|
||||
"root_dir_name": root_dir_name,
|
||||
}
|
||||
except BaseException as e:
|
||||
fail_rec += f"加载{p}插件出现问题,原因 {str(e)}\n"
|
||||
if fail_rec == "":
|
||||
return True, None
|
||||
else:
|
||||
return False, fail_rec
|
||||
|
||||
def install_plugin(repo_url: str, cached_plugins: dict):
|
||||
ppath = get_plugin_store_path()
|
||||
# 删除末尾的 /
|
||||
if repo_url.endswith("/"):
|
||||
repo_url = repo_url[:-1]
|
||||
# 得到 url 的最后一段
|
||||
d = repo_url.split("/")[-1]
|
||||
# 转换非法字符:-
|
||||
d = d.replace("-", "_")
|
||||
# 创建文件夹
|
||||
plugin_path = os.path.join(ppath, d)
|
||||
if os.path.exists(plugin_path):
|
||||
shutil.rmtree(plugin_path)
|
||||
Repo.clone_from(repo_url, to_path=plugin_path, branch='master')
|
||||
# 读取插件的requirements.txt
|
||||
if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
|
||||
if pipmain(['install', '-r', os.path.join(plugin_path, "requirements.txt")]) != 0:
|
||||
raise Exception("插件的依赖安装失败, 需要您手动 pip 安装对应插件的依赖。")
|
||||
ok, err = plugin_reload(cached_plugins, target=d)
|
||||
if not ok: raise Exception(err)
|
||||
|
||||
def uninstall_plugin(plugin_name: str, cached_plugins: dict):
|
||||
if plugin_name not in cached_plugins:
|
||||
raise Exception("插件不存在。")
|
||||
root_dir_name = cached_plugins[plugin_name]["root_dir_name"]
|
||||
ppath = get_plugin_store_path()
|
||||
del cached_plugins[plugin_name]
|
||||
if not remove_dir(os.path.join(ppath, root_dir_name)):
|
||||
raise Exception("移除插件成功,但是删除插件文件夹失败。您可以手动删除该文件夹,位于 addons/plugins/ 下。")
|
||||
|
||||
def update_plugin(plugin_name: str, cached_plugins: dict):
|
||||
if plugin_name not in cached_plugins:
|
||||
raise Exception("插件不存在。")
|
||||
ppath = get_plugin_store_path()
|
||||
root_dir_name = cached_plugins[plugin_name]["root_dir_name"]
|
||||
plugin_path = os.path.join(ppath, root_dir_name)
|
||||
repo = Repo(path = plugin_path)
|
||||
repo.remotes.origin.pull()
|
||||
# 读取插件的requirements.txt
|
||||
if os.path.exists(os.path.join(plugin_path, "requirements.txt")):
|
||||
if pipmain(['install', '-r', os.path.join(plugin_path, "requirements.txt")]) != 0:
|
||||
raise Exception("插件依赖安装失败, 需要您手动pip安装对应插件的依赖。")
|
||||
ok, err = plugin_reload(cached_plugins, target=plugin_name)
|
||||
if not ok: raise Exception(err)
|
||||
|
||||
def remove_dir(self, file_path) -> bool:
|
||||
try_cnt = 50
|
||||
while try_cnt > 0:
|
||||
if not os.path.exists(file_path):
|
||||
return False
|
||||
try:
|
||||
shutil.rmtree(file_path)
|
||||
return True
|
||||
except PermissionError as e:
|
||||
err_file_path = str(e).split("\'", 2)[1]
|
||||
if os.path.exists(err_file_path):
|
||||
os.chmod(err_file_path, stat.S_IWUSR)
|
||||
try_cnt -= 1
|
||||
Reference in New Issue
Block a user