feat 在线仓库插件安装
This commit is contained in:
parent
0dac3f1b1d
commit
fbe306ba90
@ -97,7 +97,6 @@ MoviePilot需要配套下载器和媒体服务器配合使用。
|
|||||||
- **❗COOKIECLOUD_PASSWORD:** CookieCloud端对端加密密码
|
- **❗COOKIECLOUD_PASSWORD:** CookieCloud端对端加密密码
|
||||||
- **❗COOKIECLOUD_INTERVAL:** CookieCloud同步间隔(分钟)
|
- **❗COOKIECLOUD_INTERVAL:** CookieCloud同步间隔(分钟)
|
||||||
- **❗USER_AGENT:** CookieCloud保存Cookie对应的浏览器UA,建议配置,设置后可增加连接站点的成功率,同步站点后可以在管理界面中修改
|
- **❗USER_AGENT:** CookieCloud保存Cookie对应的浏览器UA,建议配置,设置后可增加连接站点的成功率,同步站点后可以在管理界面中修改
|
||||||
- **OCR_HOST:** OCR识别服务器地址,格式:`http(s)://ip:port`,用于识别站点验证码实现自动登录获取Cookie等,不配置默认使用内建服务器`https://movie-pilot.org`,可使用 [这个镜像](https://hub.docker.com/r/jxxghp/moviepilot-ocr) 自行搭建。
|
|
||||||
---
|
---
|
||||||
- **SUBSCRIBE_MODE:** 订阅模式,`rss`/`spider`,默认`spider`,`rss`模式通过定时刷新RSS来匹配订阅(RSS地址会自动获取,也可手动维护),对站点压力小,同时可设置订阅刷新周期,24小时运行,但订阅和下载通知不能过滤和显示免费,推荐使用rss模式。
|
- **SUBSCRIBE_MODE:** 订阅模式,`rss`/`spider`,默认`spider`,`rss`模式通过定时刷新RSS来匹配订阅(RSS地址会自动获取,也可手动维护),对站点压力小,同时可设置订阅刷新周期,24小时运行,但订阅和下载通知不能过滤和显示免费,推荐使用rss模式。
|
||||||
- **SUBSCRIBE_RSS_INTERVAL:** RSS订阅模式刷新时间间隔(分钟),默认`30`分钟,不能小于5分钟。
|
- **SUBSCRIBE_RSS_INTERVAL:** RSS订阅模式刷新时间间隔(分钟),默认`30`分钟,不能小于5分钟。
|
||||||
@ -106,6 +105,9 @@ MoviePilot需要配套下载器和媒体服务器配合使用。
|
|||||||
---
|
---
|
||||||
- **AUTO_DOWNLOAD_USER:** 远程交互搜索时自动择优下载的用户ID,多个用户使用,分割,未设置需要选择资源或者回复`0`
|
- **AUTO_DOWNLOAD_USER:** 远程交互搜索时自动择优下载的用户ID,多个用户使用,分割,未设置需要选择资源或者回复`0`
|
||||||
- **❗MESSAGER:** 消息通知渠道,支持 `telegram`/`wechat`/`slack`/`synologychat`,开启多个渠道时使用`,`分隔。同时还需要配置对应渠道的环境变量,非对应渠道的变量可删除,推荐使用`telegram`
|
- **❗MESSAGER:** 消息通知渠道,支持 `telegram`/`wechat`/`slack`/`synologychat`,开启多个渠道时使用`,`分隔。同时还需要配置对应渠道的环境变量,非对应渠道的变量可删除,推荐使用`telegram`
|
||||||
|
---
|
||||||
|
- **OCR_HOST:** OCR识别服务器地址,格式:`http(s)://ip:port`,用于识别站点验证码实现自动登录获取Cookie等,不配置默认使用内建服务器`https://movie-pilot.org`,可使用 [这个镜像](https://hub.docker.com/r/jxxghp/moviepilot-ocr) 自行搭建。
|
||||||
|
- **PLUGIN_MARKET:** 插件市场仓库地址,多个地址使用`,`分隔,保留最后的/,默认为官方插件仓库:`https://raw.githubusercontent.com/jxxghp/MoviePilot-Plugins/main/`。
|
||||||
|
|
||||||
- `wechat`设置项:
|
- `wechat`设置项:
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ from app import schemas
|
|||||||
from app.core.plugin import PluginManager
|
from app.core.plugin import PluginManager
|
||||||
from app.core.security import verify_token
|
from app.core.security import verify_token
|
||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
|
from app.helper.plugin import PluginHelper
|
||||||
from app.schemas.types import SystemConfigKey
|
from app.schemas.types import SystemConfigKey
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@ -16,7 +17,25 @@ def all_plugins(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
|||||||
"""
|
"""
|
||||||
查询所有插件清单
|
查询所有插件清单
|
||||||
"""
|
"""
|
||||||
return PluginManager().get_plugin_apps()
|
# 查询本地插件
|
||||||
|
local_plugins = PluginManager().get_local_plugins()
|
||||||
|
# 在线插件
|
||||||
|
online_plugins = PluginManager().get_online_plugins()
|
||||||
|
# 全并去重,在线插件有的以在线插件为准
|
||||||
|
plugins = []
|
||||||
|
if not local_plugins:
|
||||||
|
return online_plugins
|
||||||
|
for plugin in local_plugins:
|
||||||
|
for online_plugin in online_plugins:
|
||||||
|
if plugin["id"] == online_plugin["id"]:
|
||||||
|
plugins.append(online_plugin)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
plugins.append(plugin)
|
||||||
|
for plugin in online_plugins:
|
||||||
|
if plugin not in plugins:
|
||||||
|
plugins.append(plugin)
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
@router.get("/installed", summary="已安装插件", response_model=List[str])
|
@router.get("/installed", summary="已安装插件", response_model=List[str])
|
||||||
@ -29,18 +48,34 @@ def installed_plugins(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
|||||||
|
|
||||||
@router.get("/install/{plugin_id}", summary="安装插件", response_model=schemas.Response)
|
@router.get("/install/{plugin_id}", summary="安装插件", response_model=schemas.Response)
|
||||||
def install_plugin(plugin_id: str,
|
def install_plugin(plugin_id: str,
|
||||||
|
repo_url: str = "",
|
||||||
|
force: bool = False,
|
||||||
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
_: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||||
"""
|
"""
|
||||||
安装插件
|
安装插件
|
||||||
"""
|
"""
|
||||||
# 已安装插件
|
# 已安装插件
|
||||||
install_plugins = SystemConfigOper().get(SystemConfigKey.UserInstalledPlugins) or []
|
install_plugins = SystemConfigOper().get(SystemConfigKey.UserInstalledPlugins) or []
|
||||||
|
# 重载标志
|
||||||
|
reload_flag = False
|
||||||
|
# 如果是非本地括件,或者强制安装时,则需要下载安装
|
||||||
|
if repo_url and (force or plugin_id not in PluginManager().get_plugin_ids()):
|
||||||
|
# 下载安装
|
||||||
|
state, msg = PluginHelper().install(pid=plugin_id, repo_url=repo_url)
|
||||||
|
if state:
|
||||||
|
# 安装成功
|
||||||
|
reload_flag = True
|
||||||
|
else:
|
||||||
|
# 安装失败
|
||||||
|
return schemas.Response(success=False, msg=msg)
|
||||||
# 安装插件
|
# 安装插件
|
||||||
if plugin_id not in install_plugins:
|
if plugin_id not in install_plugins:
|
||||||
|
reload_flag = True
|
||||||
install_plugins.append(plugin_id)
|
install_plugins.append(plugin_id)
|
||||||
# 保存设置
|
# 保存设置
|
||||||
SystemConfigOper().set(SystemConfigKey.UserInstalledPlugins, install_plugins)
|
SystemConfigOper().set(SystemConfigKey.UserInstalledPlugins, install_plugins)
|
||||||
# 重载插件管理器
|
# 重载插件管理器
|
||||||
|
if reload_flag:
|
||||||
PluginManager().init_config()
|
PluginManager().init_config()
|
||||||
return schemas.Response(success=True)
|
return schemas.Response(success=True)
|
||||||
|
|
||||||
|
@ -210,10 +210,8 @@ class Settings(BaseSettings):
|
|||||||
OVERWRITE_MODE: str = "size"
|
OVERWRITE_MODE: str = "size"
|
||||||
# 大内存模式
|
# 大内存模式
|
||||||
BIG_MEMORY_MODE: bool = False
|
BIG_MEMORY_MODE: bool = False
|
||||||
# 插件市场地址
|
# 插件市场仓库地址,多个地址使用,分隔,地址以/结尾
|
||||||
PLUGIN_MARKET: str = "https://movie-pilot.org/pluginmarket"
|
PLUGIN_MARKET: str = "https://raw.githubusercontent.com/jxxghp/MoviePilot-Plugins/main/"
|
||||||
# 资源包更新地址
|
|
||||||
RESOURCE_HOST: str = "https://movie-pilot.org/resources"
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def INNER_CONFIG_PATH(self):
|
def INNER_CONFIG_PATH(self):
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import traceback
|
import traceback
|
||||||
from typing import List, Any, Dict, Tuple
|
from typing import List, Any, Dict, Tuple
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
from app.core.event import eventmanager
|
from app.core.event import eventmanager
|
||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
from app.helper.module import ModuleHelper
|
from app.helper.module import ModuleHelper
|
||||||
|
from app.helper.plugin import PluginHelper
|
||||||
from app.helper.sites import SitesHelper
|
from app.helper.sites import SitesHelper
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.schemas.types import SystemConfigKey
|
from app.schemas.types import SystemConfigKey
|
||||||
from app.utils.object import ObjectUtils
|
from app.utils.object import ObjectUtils
|
||||||
from app.utils.singleton import Singleton
|
from app.utils.singleton import Singleton
|
||||||
|
from app.utils.string import StringUtils
|
||||||
|
|
||||||
|
|
||||||
class PluginManager(metaclass=Singleton):
|
class PluginManager(metaclass=Singleton):
|
||||||
@ -26,6 +29,7 @@ class PluginManager(metaclass=Singleton):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.siteshelper = SitesHelper()
|
self.siteshelper = SitesHelper()
|
||||||
|
self.pluginhelper = PluginHelper()
|
||||||
self.init_config()
|
self.init_config()
|
||||||
|
|
||||||
def init_config(self):
|
def init_config(self):
|
||||||
@ -188,9 +192,91 @@ class PluginManager(metaclass=Singleton):
|
|||||||
"""
|
"""
|
||||||
return list(self._plugins.keys())
|
return list(self._plugins.keys())
|
||||||
|
|
||||||
def get_plugin_apps(self) -> List[dict]:
|
def get_online_plugins(self) -> List[Dict[str, dict]]:
|
||||||
"""
|
"""
|
||||||
获取所有插件信息
|
获取所有在线插件信息
|
||||||
|
"""
|
||||||
|
# 返回值
|
||||||
|
all_confs = []
|
||||||
|
if not settings.PLUGIN_MARKET:
|
||||||
|
return all_confs
|
||||||
|
# 已安装插件
|
||||||
|
installed_apps = self.systemconfig.get(SystemConfigKey.UserInstalledPlugins) or []
|
||||||
|
# 线上插件列表
|
||||||
|
markets = settings.PLUGIN_MARKET.split(",")
|
||||||
|
for market in markets:
|
||||||
|
online_plugins = self.pluginhelper.get_plugins(market) or {}
|
||||||
|
for pid, plugin in online_plugins.items():
|
||||||
|
# 运行状插件
|
||||||
|
plugin_obj = self._running_plugins.get(pid)
|
||||||
|
# 非运行态插件
|
||||||
|
plugin_static = self._plugins.get(pid)
|
||||||
|
# 基本属性
|
||||||
|
conf = {}
|
||||||
|
# ID
|
||||||
|
conf.update({"id": pid})
|
||||||
|
# 安装状态,是否有新版本
|
||||||
|
if plugin_static:
|
||||||
|
# 已安装
|
||||||
|
if pid in installed_apps:
|
||||||
|
conf.update({"installed": True})
|
||||||
|
else:
|
||||||
|
conf.update({"installed": False})
|
||||||
|
conf.update({"has_update": False})
|
||||||
|
if plugin_obj:
|
||||||
|
installed_version = getattr(plugin_static, "plugin_version")
|
||||||
|
if StringUtils.compare_version(installed_version, plugin.get("version")) < 0:
|
||||||
|
# 需要更新
|
||||||
|
conf.update({"installed": False})
|
||||||
|
conf.update({"has_update": True})
|
||||||
|
else:
|
||||||
|
# 未安装
|
||||||
|
conf.update({"installed": False})
|
||||||
|
conf.update({"has_update": False})
|
||||||
|
# 运行状态
|
||||||
|
if plugin_obj and hasattr(plugin_obj, "get_state"):
|
||||||
|
conf.update({"state": plugin_obj.get_state()})
|
||||||
|
else:
|
||||||
|
conf.update({"state": False})
|
||||||
|
# 是否有详情页面
|
||||||
|
conf.update({"has_page": False})
|
||||||
|
if plugin_obj and hasattr(plugin_obj, "get_page"):
|
||||||
|
if ObjectUtils.check_method(plugin_obj.get_page):
|
||||||
|
conf.update({"has_page": True})
|
||||||
|
# 权限
|
||||||
|
if plugin.get("level"):
|
||||||
|
conf.update({"auth_level": plugin.get("level")})
|
||||||
|
if self.siteshelper.auth_level < plugin.get("level"):
|
||||||
|
continue
|
||||||
|
# 名称
|
||||||
|
if plugin.get("name"):
|
||||||
|
conf.update({"plugin_name": plugin.get("name")})
|
||||||
|
# 描述
|
||||||
|
if plugin.get("description"):
|
||||||
|
conf.update({"plugin_desc": plugin.get("description")})
|
||||||
|
# 版本
|
||||||
|
if plugin.get("version"):
|
||||||
|
conf.update({"plugin_version": plugin.get("version")})
|
||||||
|
# 图标
|
||||||
|
if plugin.get("icon"):
|
||||||
|
conf.update({"plugin_icon": plugin.get("icon")})
|
||||||
|
# 主题色
|
||||||
|
if plugin.get("color"):
|
||||||
|
conf.update({"plugin_color": plugin.get("color")})
|
||||||
|
# 作者
|
||||||
|
if plugin.get("author"):
|
||||||
|
conf.update({"plugin_author": plugin.get("author")})
|
||||||
|
# 仓库链接
|
||||||
|
conf.update({"repo_url": market})
|
||||||
|
# 本地标志
|
||||||
|
conf.update({"is_local": False})
|
||||||
|
# 汇总
|
||||||
|
all_confs.append(conf)
|
||||||
|
return all_confs
|
||||||
|
|
||||||
|
def get_local_plugins(self) -> List[Dict[str, dict]]:
|
||||||
|
"""
|
||||||
|
获取所有本地已下载的插件信息
|
||||||
"""
|
"""
|
||||||
# 返回值
|
# 返回值
|
||||||
all_confs = []
|
all_confs = []
|
||||||
@ -209,7 +295,7 @@ class PluginManager(metaclass=Singleton):
|
|||||||
else:
|
else:
|
||||||
conf.update({"installed": False})
|
conf.update({"installed": False})
|
||||||
# 运行状态
|
# 运行状态
|
||||||
if plugin_obj and hasattr(plugin, "get_state"):
|
if plugin_obj and hasattr(plugin_obj, "get_state"):
|
||||||
conf.update({"state": plugin_obj.get_state()})
|
conf.update({"state": plugin_obj.get_state()})
|
||||||
else:
|
else:
|
||||||
conf.update({"state": False})
|
conf.update({"state": False})
|
||||||
@ -221,6 +307,7 @@ class PluginManager(metaclass=Singleton):
|
|||||||
conf.update({"has_page": False})
|
conf.update({"has_page": False})
|
||||||
# 权限
|
# 权限
|
||||||
if hasattr(plugin, "auth_level"):
|
if hasattr(plugin, "auth_level"):
|
||||||
|
conf.update({"auth_level": plugin.auth_level})
|
||||||
if self.siteshelper.auth_level < plugin.auth_level:
|
if self.siteshelper.auth_level < plugin.auth_level:
|
||||||
continue
|
continue
|
||||||
# 名称
|
# 名称
|
||||||
@ -244,6 +331,10 @@ class PluginManager(metaclass=Singleton):
|
|||||||
# 作者链接
|
# 作者链接
|
||||||
if hasattr(plugin, "author_url"):
|
if hasattr(plugin, "author_url"):
|
||||||
conf.update({"author_url": plugin.author_url})
|
conf.update({"author_url": plugin.author_url})
|
||||||
|
# 是否需要更新
|
||||||
|
conf.update({"has_update": False})
|
||||||
|
# 本地标志
|
||||||
|
conf.update({"is_local": True})
|
||||||
# 汇总
|
# 汇总
|
||||||
all_confs.append(conf)
|
all_confs.append(conf)
|
||||||
return all_confs
|
return all_confs
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
import json
|
||||||
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict, Tuple
|
||||||
|
|
||||||
from cachetools import TTLCache, cached
|
from cachetools import TTLCache, cached
|
||||||
|
|
||||||
|
from app.core.config import settings
|
||||||
|
from app.utils.http import RequestUtils
|
||||||
from app.utils.singleton import Singleton
|
from app.utils.singleton import Singleton
|
||||||
|
from app.utils.system import SystemUtils
|
||||||
|
|
||||||
|
|
||||||
class PluginHelper(metaclass=Singleton):
|
class PluginHelper(metaclass=Singleton):
|
||||||
@ -12,20 +17,77 @@ class PluginHelper(metaclass=Singleton):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@cached(cache=TTLCache(maxsize=1, ttl=1800))
|
@cached(cache=TTLCache(maxsize=1, ttl=1800))
|
||||||
def get_plugins(self) -> Dict[str, dict]:
|
def get_plugins(self, repo_url: str) -> Dict[str, dict]:
|
||||||
"""
|
"""
|
||||||
获取Github所有最新插件列表
|
获取Github所有最新插件列表
|
||||||
|
:param repo_url: Github仓库地址
|
||||||
"""
|
"""
|
||||||
pass
|
if not repo_url:
|
||||||
|
return {}
|
||||||
|
res = RequestUtils(proxies=settings.PROXY).get_res(f"{repo_url}package.json")
|
||||||
|
if res:
|
||||||
|
return json.loads(res.text)
|
||||||
|
return {}
|
||||||
|
|
||||||
def download(self, name: str, dest: Path) -> bool:
|
@staticmethod
|
||||||
"""
|
def install(pid: str, repo_url: str) -> Tuple[bool, str]:
|
||||||
下载插件到本地
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def install(self, name: str) -> bool:
|
|
||||||
"""
|
"""
|
||||||
安装插件
|
安装插件
|
||||||
"""
|
"""
|
||||||
pass
|
if not pid or not repo_url:
|
||||||
|
return False, "参数错误"
|
||||||
|
# 从Github的repo_url获取用户和项目名
|
||||||
|
try:
|
||||||
|
user, repo = repo_url.split("/")[-4:-2]
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"不支持的插件仓库地址格式:{str(e)}"
|
||||||
|
if not user or not repo:
|
||||||
|
return False, "不支持的插件仓库地址格式"
|
||||||
|
if SystemUtils.is_frozen():
|
||||||
|
return False, "可执行文件模式下,只能安装本地插件"
|
||||||
|
# 获取插件的文件列表
|
||||||
|
"""
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "__init__.py",
|
||||||
|
"path": "plugins/autobackup/__init__.py",
|
||||||
|
"sha": "cd10eba3f0355d61adeb35561cb26a0a36c15a6c",
|
||||||
|
"size": 12385,
|
||||||
|
"url": "https://api.github.com/repos/jxxghp/MoviePilot-Plugins/contents/plugins/autobackup/__init__.py?ref=main",
|
||||||
|
"html_url": "https://github.com/jxxghp/MoviePilot-Plugins/blob/main/plugins/autobackup/__init__.py",
|
||||||
|
"git_url": "https://api.github.com/repos/jxxghp/MoviePilot-Plugins/git/blobs/cd10eba3f0355d61adeb35561cb26a0a36c15a6c",
|
||||||
|
"download_url": "https://raw.githubusercontent.com/jxxghp/MoviePilot-Plugins/main/plugins/autobackup/__init__.py",
|
||||||
|
"type": "file",
|
||||||
|
"_links": {
|
||||||
|
"self": "https://api.github.com/repos/jxxghp/MoviePilot-Plugins/contents/plugins/autobackup/__init__.py?ref=main",
|
||||||
|
"git": "https://api.github.com/repos/jxxghp/MoviePilot-Plugins/git/blobs/cd10eba3f0355d61adeb35561cb26a0a36c15a6c",
|
||||||
|
"html": "https://github.com/jxxghp/MoviePilot-Plugins/blob/main/plugins/autobackup/__init__.py"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"""
|
||||||
|
file_api = f"https://api.github.com/repos/{user}/{repo}/contents/plugins/{pid.lower()}"
|
||||||
|
res = RequestUtils(proxies=settings.PROXY).get_res(file_api)
|
||||||
|
if not res or res.status_code != 200:
|
||||||
|
return False, f"连接仓库失败:{res.status_code} - {res.reason}"
|
||||||
|
ret_json = res.json()
|
||||||
|
if ret_json and ret_json[0].get("message") == "Not Found":
|
||||||
|
return False, "插件在仓库中不存在"
|
||||||
|
# 本地存在时先删除
|
||||||
|
plugin_dir = Path(settings.ROOT_PATH) / "app" / "plugins" / pid.lower()
|
||||||
|
if plugin_dir.exists():
|
||||||
|
shutil.rmtree(plugin_dir)
|
||||||
|
# 下载所有文件
|
||||||
|
for item in ret_json:
|
||||||
|
if item.get("download_url"):
|
||||||
|
# 下载插件文件
|
||||||
|
res = RequestUtils(proxies=settings.PROXY).get_res(item["download_url"])
|
||||||
|
if not res or res.status_code != 200:
|
||||||
|
return False, f"下载文件 {item.get('name')} 失败:{res.status_code} - {res.reason}"
|
||||||
|
# 创建插件文件夹
|
||||||
|
file_path = Path(settings.ROOT_PATH) / "app" / item.get("path")
|
||||||
|
if not file_path.parent.exists():
|
||||||
|
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with open(file_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(res.text)
|
||||||
|
return True, ""
|
||||||
|
@ -34,3 +34,9 @@ class Plugin(BaseModel):
|
|||||||
state: Optional[bool] = False
|
state: Optional[bool] = False
|
||||||
# 是否有详情页面
|
# 是否有详情页面
|
||||||
has_page: Optional[bool] = False
|
has_page: Optional[bool] = False
|
||||||
|
# 是否有新版本
|
||||||
|
has_update: Optional[bool] = False
|
||||||
|
# 是否本地
|
||||||
|
is_local: Optional[bool] = False
|
||||||
|
# 仓库地址
|
||||||
|
repo_url: Optional[str] = None
|
||||||
|
@ -688,3 +688,26 @@ class StringUtils:
|
|||||||
break
|
break
|
||||||
|
|
||||||
return ''.join(common_prefix)
|
return ''.join(common_prefix)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compare_version(v1: str, v2: str) -> int:
|
||||||
|
"""
|
||||||
|
比较两个版本号的大小,v1 > v2时返回1,v1 < v2时返回-1,v1 = v2时返回0
|
||||||
|
"""
|
||||||
|
if not v1 or not v2:
|
||||||
|
return 0
|
||||||
|
v1 = v1.replace('v', '')
|
||||||
|
v2 = v2.replace('v', '')
|
||||||
|
v1 = [int(x) for x in v1.split('.')]
|
||||||
|
v2 = [int(x) for x in v2.split('.')]
|
||||||
|
for i in range(min(len(v1), len(v2))):
|
||||||
|
if v1[i] > v2[i]:
|
||||||
|
return 1
|
||||||
|
elif v1[i] < v2[i]:
|
||||||
|
return -1
|
||||||
|
if len(v1) > len(v2):
|
||||||
|
return 1
|
||||||
|
elif len(v1) < len(v2):
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user