feat: 新增上传重试次数限制和上传结果webhook通知 (#1355)
* feat: 新增重试上传次数限制 * feat: 新增上传后WebHook设置选项 * feat: 新增上传webhook逻辑 * mod: 将功能配置移至直播配置中的更多设置 * mod: 修改webhook传输的信息 * mod: 修正错误的数据类型 Refactor webhook upload logic and update build configs Replaces the 'uploaded_webhook' field with a 'webhook' postprocessor in the upload workflow, updating both backend and frontend to support this change. Updates the Tauri app to use the correct resource path for the sidecar binary, adjusts process management, and modifies build and workflow scripts for improved compatibility. Also bumps the 'stream-gears' crate version to 1.0.1 and adds '_internal' to .gitignore. Update pyinstaller-publish.yml Fix pyinstaller copy metadata (#1357) * Update tauri.conf.json * Update biliup.spec Update pyinstaller-publish.yml feat: 新增上传重试次数限制和上传结果webhook通知 (#1355) * feat: 新增重试上传次数限制 * feat: 新增上传后WebHook设置选项 * feat: 新增上传webhook逻辑 * mod: 将功能配置移至直播配置中的更多设置 * mod: 修改webhook传输的信息 * mod: 修正错误的数据类型 Refactor webhook upload logic and update build configs Replaces the 'uploaded_webhook' field with a 'webhook' postprocessor in the upload workflow, updating both backend and frontend to support this change. Updates the Tauri app to use the correct resource path for the sidecar binary, adjusts process management, and modifies build and workflow scripts for improved compatibility. Also bumps the 'stream-gears' crate version to 1.0.1 and adds '_internal' to .gitignore.
This commit is contained in:
1 parent
18c523e63c
commit
bd69b76124
18 files changed
+230
-128
No files matched your search
@@ -1,53 +1,53 @@
|
||||
#name: Docker
|
||||
#
|
||||
#on:
|
||||
# push:
|
||||
# # Publish `master` as Docker `latest` image.
|
||||
# branches:
|
||||
# - master
|
||||
#
|
||||
# # Publish `v1.2.3` tags as releases.
|
||||
# tags:
|
||||
# - v*
|
||||
#
|
||||
# # Run tests for any PRs.
|
||||
# pull_request:
|
||||
#
|
||||
#jobs:
|
||||
# # Run tests.
|
||||
# # See also https://docs.docker.com/docker-hub/builds/automated-testing/
|
||||
# test:
|
||||
# runs-on: ubuntu-latest
|
||||
#
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
#
|
||||
# - name: Get PR number, repo URL, and branch name
|
||||
# run: |
|
||||
# if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
|
||||
# pr_number=$(jq -r .number < "$GITHUB_EVENT_PATH")
|
||||
# repo_url=$(jq -r .pull_request.head.repo.clone_url < "$GITHUB_EVENT_PATH")
|
||||
# branch_name=$GITHUB_HEAD_REF
|
||||
# else
|
||||
# pr_number=""
|
||||
# repo_url=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY
|
||||
# branch_name="master"
|
||||
# fi
|
||||
# echo "PR_NUMBER=$pr_number" >> $GITHUB_ENV
|
||||
# echo "REPO_URL=$repo_url" >> $GITHUB_ENV
|
||||
# echo "BRANCH_NAME=$branch_name" >> $GITHUB_ENV
|
||||
#
|
||||
# - name: Run tests
|
||||
# run: |
|
||||
# if [ -f docker-compose.test.yml ]; then
|
||||
# docker-compose --file docker-compose.test.yml build
|
||||
# docker-compose --file docker-compose.test.yml run sut
|
||||
# else
|
||||
# docker build \
|
||||
# --build-arg repo_url=$REPO_URL \
|
||||
# --build-arg branch_name=$BRANCH_NAME \
|
||||
# . --file Dockerfile
|
||||
# fi
|
||||
name: Docker
|
||||
|
||||
on:
|
||||
push:
|
||||
# Publish `master` as Docker `latest` image.
|
||||
branches:
|
||||
- master
|
||||
|
||||
# Publish `v1.2.3` tags as releases.
|
||||
tags:
|
||||
- v*
|
||||
|
||||
# Run tests for any PRs.
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
# Run tests.
|
||||
# See also https://docs.docker.com/docker-hub/builds/automated-testing/
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Get PR number, repo URL, and branch name
|
||||
run: |
|
||||
if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
|
||||
pr_number=$(jq -r .number < "$GITHUB_EVENT_PATH")
|
||||
repo_url=$(jq -r .pull_request.head.repo.clone_url < "$GITHUB_EVENT_PATH")
|
||||
branch_name=$GITHUB_HEAD_REF
|
||||
else
|
||||
pr_number=""
|
||||
repo_url=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY
|
||||
branch_name="master"
|
||||
fi
|
||||
echo "PR_NUMBER=$pr_number" >> $GITHUB_ENV
|
||||
echo "REPO_URL=$repo_url" >> $GITHUB_ENV
|
||||
echo "BRANCH_NAME=$branch_name" >> $GITHUB_ENV
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
if [ -f docker-compose.test.yml ]; then
|
||||
docker-compose --file docker-compose.test.yml build
|
||||
docker-compose --file docker-compose.test.yml run sut
|
||||
else
|
||||
docker build \
|
||||
--build-arg repo_url=$REPO_URL \
|
||||
--build-arg branch_name=$BRANCH_NAME \
|
||||
. --file Dockerfile
|
||||
fi
|
||||
#
|
||||
#
|
||||
# # Push image to GitHub Packages.
|
||||
|
||||
@@ -49,7 +49,8 @@ jobs:
|
||||
echo "Moving $SOURCE_PATH to $DESTINATION_PATH"
|
||||
|
||||
# 使用PowerShell的Move-Item命令进行移动和重命名
|
||||
Move-Item -Path $SOURCE_PATH -Destination $DESTINATION_PATH
|
||||
#Move-Item -Path $SOURCE_PATH -Destination $DESTINATION_PATH
|
||||
cp -r dist/bbup/* tauri-app/src-tauri/binaries
|
||||
shell: pwsh
|
||||
# Optionally verify that it works (provided that it does not need user interaction)
|
||||
#- run: ./dist/your-code/your-code
|
||||
|
||||
+2
-1
@@ -44,4 +44,5 @@ data/
|
||||
.next
|
||||
out
|
||||
downloads/
|
||||
target
|
||||
target
|
||||
_internal
|
||||
Generated
+1
-1
@@ -5289,7 +5289,7 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "stream-gears"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"biliup",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
## 📜 使用文档
|
||||
|
||||
- [使用文档 »](https://docs.biliup.me)
|
||||
- [使用文档 »](https://docs.biliup.rs)
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
|
||||
+27
-15
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
Form,
|
||||
Modal,
|
||||
Input,
|
||||
Notification,
|
||||
Typography,
|
||||
Collapse,
|
||||
@@ -30,9 +31,9 @@ const TemplateModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
|
||||
const maskStyle = isOpen
|
||||
? {}
|
||||
: {
|
||||
WebkitMaskImage:
|
||||
'linear-gradient(to bottom, black 0%, rgba(0, 0, 0, 1) 60%, rgba(0, 0, 0, 0.2) 80%, transparent 100%)',
|
||||
}
|
||||
WebkitMaskImage:
|
||||
'linear-gradient(to bottom, black 0%, rgba(0, 0, 0, 1) 60%, rgba(0, 0, 0, 0.2) 80%, transparent 100%)',
|
||||
}
|
||||
|
||||
const collapsed = (
|
||||
<div className="semi-form-field-extra">
|
||||
@@ -44,8 +45,17 @@ const TemplateModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
|
||||
<code>mv = backup/</code> 移动文件到backup目录下
|
||||
<br />
|
||||
<code>run = echo hello!</code> 使用当前运行用户在 shell 执行任意命令,
|
||||
<br />
|
||||
<code>webhook = url</code>
|
||||
不填写则是默认的逻辑,上传失败后重试直至达到最大重试次数
|
||||
<br />
|
||||
填写后,<strong>是否上传成功的判断由WebHook给出</strong>,但失败最大重试次数仍会被限制(前往空间配置-上传重试次数限制处配置)
|
||||
<br />
|
||||
上传任务完成后,会将主播信息、稿件信息、捕获的异常等以post模式发送给WebHook,<strong>当WebHook返回success字符串时视为上传成功</strong>,执行正常后处理流程,否则返回其他任意内容或请求失败均尝试重新上传
|
||||
<br />
|
||||
<Text type="danger">注意风险。</Text>视频文件路径作为标准输入传入
|
||||
{/* TODO: 在这里塞插件仓库 */}
|
||||
<br />
|
||||
TODO: 在这里塞插件仓库
|
||||
</div>
|
||||
)
|
||||
const toggle = () => {
|
||||
@@ -176,6 +186,7 @@ const TemplateModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
|
||||
<Form.Select.Option value="run">run(运行)</Form.Select.Option>
|
||||
<Form.Select.Option value="mv">mv(移动到)</Form.Select.Option>
|
||||
<Form.Select.Option value="rm">rm(删除文件)</Form.Select.Option>
|
||||
<Form.Select.Option value="webhook">webhook</Form.Select.Option>
|
||||
</Form.Select>
|
||||
{api.current?.getValue(field)?.cmd !== 'rm' ? (
|
||||
<Form.Input
|
||||
@@ -184,6 +195,7 @@ const TemplateModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
|
||||
labelPosition="inset"
|
||||
rules={[{ required: true, message }]}
|
||||
style={{ width: 300, marginRight: 16 }}
|
||||
placeholder={ api.current?.getValue(field)?.cmd === 'webhook' ? 'https://example.com/notify' : undefined }
|
||||
></Form.Input>
|
||||
) : null}
|
||||
<Button
|
||||
@@ -224,19 +236,19 @@ const TemplateModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
|
||||
placeholder=" "
|
||||
extraText={
|
||||
<div style={{ fontSize: '14px' }}>
|
||||
如果设置了录制时间范围,不在时间范围内,将不进行录制<br/>
|
||||
如果设置了录制时间范围,不在时间范围内,将不进行录制<br />
|
||||
下载器需使用ffmpeg或streamlink
|
||||
</div>
|
||||
}
|
||||
label={{
|
||||
text: '录制时间范围',
|
||||
optional: true,
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
marginBottom: '4px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '1px solid var(--semi-color-border)',
|
||||
}
|
||||
text: '录制时间范围',
|
||||
optional: true,
|
||||
style: {
|
||||
fontSize: '18px',
|
||||
marginBottom: '4px',
|
||||
paddingBottom: '8px',
|
||||
borderBottom: '1px solid var(--semi-color-border)',
|
||||
}
|
||||
}}
|
||||
style={{ width: 176 }}
|
||||
/>
|
||||
@@ -245,7 +257,7 @@ const TemplateModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
|
||||
{({ add, arrayFields }) => (
|
||||
<Form.Section text="不录制关键词">
|
||||
<div className="semi-form-field-extra">
|
||||
如果房间名包含关键词,则停止或不录制该场直播,每个关键词需单独一行<br/>
|
||||
如果房间名包含关键词,则停止或不录制该场直播,每个关键词需单独一行<br />
|
||||
暂不支持<strong>cc直播</strong>、<strong>yy直播</strong>、<strong>twitch直播</strong>
|
||||
</div>
|
||||
<Button icon={<IconPlusCircle />} onClick={add} theme="light">
|
||||
@@ -268,7 +280,7 @@ const TemplateModal: React.FC<TemplateModalProps> = ({ children, entity, onOk })
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</Form.Section>
|
||||
</Form.Section>
|
||||
)}
|
||||
</ArrayField>
|
||||
|
||||
|
||||
@@ -339,6 +339,18 @@ const Global: React.FC = () => {
|
||||
}}
|
||||
showClear={true}
|
||||
/>
|
||||
<Form.InputNumber
|
||||
field="max_upload_limit"
|
||||
placeholder={8}
|
||||
extraText="录播上传次数上限,防止因意外情况如B站接口抽风、录播本身损坏导致录播反复上传浪费宽带或被B站风控(注:限制是记录在程序上下文中的,重启程序会重置上传次数限制;且为了保证尽量不改动老用户使用逻辑,默认将此值设置为一个较大的值,一般推荐设置为2-3)"
|
||||
label="上传重试次数限制(max_upload_limit)"
|
||||
style={{ width: '100%' }}
|
||||
fieldStyle={{
|
||||
alignSelf: 'stretch',
|
||||
padding: 0,
|
||||
}}
|
||||
showClear={true}
|
||||
/>
|
||||
|
||||
<Form.InputNumber
|
||||
field="pool2_size"
|
||||
|
||||
@@ -89,7 +89,7 @@ const Edit = () => {
|
||||
up_close_danmu: values?.interaction.includes('up_close_danmu') ? 1 : 0,
|
||||
open_elec: values?.open_elec ? 1 : 0,
|
||||
no_reprint: values?.no_reprint ? 1 : 0,
|
||||
is_only_self: values?.is_only_self ? 1: 0,
|
||||
is_only_self: values?.is_only_self ? 1 : 0,
|
||||
mission_id: values?.mission_id,
|
||||
dtime: values?.isDtime ? values?.dtime : null,
|
||||
credits: values?.credits ?? null,
|
||||
|
||||
+14
-3
@@ -6,7 +6,7 @@ binaries = []
|
||||
hiddenimports = []
|
||||
tmp_ret = collect_all('biliup.plugins')
|
||||
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
|
||||
datas += copy_metadata('biliup')
|
||||
# datas += copy_metadata('biliup')
|
||||
|
||||
a = Analysis(
|
||||
['biliup\\__main__.py'],
|
||||
@@ -26,8 +26,8 @@ pyz = PYZ(a.pure)
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[], # a.binaries 从这里移除
|
||||
[], # a.datas 从这里移除
|
||||
[],
|
||||
name='biliup',
|
||||
debug=False,
|
||||
@@ -44,3 +44,14 @@ exe = EXE(
|
||||
entitlements_file=None,
|
||||
icon=['public\\logo.png'],
|
||||
)
|
||||
|
||||
# One-Folder Mode
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='bbup' # 这是最终生成的文件夹的名称
|
||||
)
|
||||
+2
-1
@@ -56,8 +56,9 @@ if (3, 10, 6) > sys.version_info >= (3, 8) and platform.system() == 'Windows':
|
||||
proactor_events._ProactorBasePipeTransport.__del__ = silence_event_loop_closed(
|
||||
proactor_events._ProactorBasePipeTransport.__del__)
|
||||
|
||||
|
||||
IS_FROZEN = False
|
||||
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
|
||||
import multiprocessing
|
||||
multiprocessing.freeze_support()
|
||||
IS_FROZEN = True
|
||||
print('running in a PyInstaller bundle')
|
||||
+11
-2
@@ -8,7 +8,7 @@ import shutil
|
||||
|
||||
import biliup.common.reload
|
||||
from biliup.config import config
|
||||
from biliup import __version__, LOG_CONF
|
||||
from biliup import __version__, LOG_CONF, IS_FROZEN
|
||||
from biliup.common.Daemon import Daemon
|
||||
from biliup.common.reload import AutoReload
|
||||
from biliup.common.log import DebugLevelFilter
|
||||
@@ -19,7 +19,16 @@ def arg_parser():
|
||||
parser = argparse.ArgumentParser(description='Stream download and upload, not only for bilibili.')
|
||||
parser.add_argument('--version', action='version', version=f"v{__version__}")
|
||||
parser.add_argument('-H', help='web api host [default: 0.0.0.0]', dest='host')
|
||||
parser.add_argument('-P', help='web api port [default: 19159]', default=19159, dest='port')
|
||||
if IS_FROZEN:
|
||||
parser.add_argument(
|
||||
'-P',
|
||||
help='web api port (REQUIRED)',
|
||||
dest='port',
|
||||
required=True,
|
||||
type=int
|
||||
)
|
||||
else:
|
||||
parser.add_argument('-P', help='web api port [default: 19159]', default=19159, dest='port')
|
||||
parser.add_argument('--no-http', action='store_true', help='disable web api')
|
||||
parser.add_argument('--static-dir', help='web static files directory for custom ui')
|
||||
parser.add_argument('--password', help='web ui password ,default username is biliup', dest='password')
|
||||
|
||||
+2
-1
@@ -25,7 +25,8 @@ def create_event_manager():
|
||||
app.context['url_upload_count'] = {}
|
||||
# 正在上传的文件 用于同时上传一个url的时候过滤掉正在上传的
|
||||
app.context['upload_filename'] = []
|
||||
|
||||
# 记录每个文件被上传的次数
|
||||
app.context['file_upload_count'] = {}
|
||||
# 边录边传的下载器使用的map
|
||||
app.context['sync_downloader_map'] = {}
|
||||
return app
|
||||
|
||||
@@ -31,6 +31,7 @@ class Config(UserDict):
|
||||
def load_from_db(self, db):
|
||||
from biliup.database.models import Configuration, LiveStreamers
|
||||
context = {
|
||||
'file_upload_count': self.data.get('file_upload_count', {}),
|
||||
'url_upload_count': self.data.get('url_upload_count', {}),
|
||||
'upload_filename': self.data.get('upload_filename', []),
|
||||
'PluginInfo': self.data.get('PluginInfo'),
|
||||
|
||||
@@ -26,6 +26,25 @@ class UploadBase:
|
||||
self.data: dict = data
|
||||
self.post_processor = postprocessor
|
||||
|
||||
# 此方法会返回文件列表中上传次数最大文件的上传次数
|
||||
@staticmethod
|
||||
def get_max_upload_count(filelist: List[FileInfo]) -> int:
|
||||
from biliup.handler import event_manager
|
||||
with NamedLock("file_upload_count"):
|
||||
file_upload_count = event_manager.context['file_upload_count']
|
||||
for file_name in os.listdir('.'):
|
||||
if file_name not in file_upload_count:
|
||||
file_upload_count[file_name] = 0
|
||||
for file_name in list(file_upload_count):
|
||||
if file_name not in os.listdir('.'):
|
||||
del file_upload_count[file_name]
|
||||
max = 0
|
||||
for item in filelist:
|
||||
if file_upload_count[item.video] > max:
|
||||
max = file_upload_count[item.video]
|
||||
file_upload_count[item.video] += 1
|
||||
return max
|
||||
|
||||
@staticmethod
|
||||
def file_list(index) -> List[FileInfo]:
|
||||
from biliup.handler import event_manager
|
||||
|
||||
+34
-1
@@ -2,6 +2,7 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import requests
|
||||
import subprocess
|
||||
import time
|
||||
from functools import reduce
|
||||
@@ -126,7 +127,39 @@ def process_upload(stream_info):
|
||||
i += 1
|
||||
data, _ = fmt_title_and_desc({**data, "name": name}) # 如果 restart, data 中会缺失 name 项
|
||||
stream_info.update(data)
|
||||
filelist = upload(stream_info)
|
||||
|
||||
upload_count = UploadBase.get_max_upload_count(file_list)
|
||||
filelist = file_list
|
||||
if upload_count < config.get("max_upload_limit", 999):
|
||||
logger.info(f"开始第{upload_count+1}次上传: {name}")
|
||||
|
||||
# 上传webhook部分
|
||||
expt = None
|
||||
try:
|
||||
filelist = upload(stream_info)
|
||||
except Exception as e:
|
||||
expt = e
|
||||
finally:
|
||||
post_processor = config['streamers'].get(name, {}).get("postprocessor", None)
|
||||
if post_processor is not None:
|
||||
for processor in post_processor:
|
||||
if isinstance(processor, dict) and processor.get('webhook') is not None:
|
||||
|
||||
logger.info(f"{name}发送webhook: {processor.get('webhook')}")
|
||||
res = requests.post(
|
||||
url = processor.get('webhook'),
|
||||
json = {
|
||||
"stream_info": {**stream_info, **config['streamers'][stream_info['name']]},
|
||||
"caught_error": str(expt) if expt else ""
|
||||
}
|
||||
).text
|
||||
logger.info(f"{name}发送webhook: {res}")
|
||||
if res != "success":
|
||||
raise Exception(res)
|
||||
|
||||
else:
|
||||
logger.warning(f"上传次数达到阈值: {name}")
|
||||
|
||||
if filelist:
|
||||
uploaded(name, stream_info.get('live_cover_path'), filelist)
|
||||
except Exception:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "stream-gears"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::env;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tauri::{Emitter, Manager, RunEvent};
|
||||
use tauri::path::BaseDirectory;
|
||||
use tauri_plugin_shell::process::{CommandChild, CommandEvent, Encoding};
|
||||
use tauri_plugin_shell::ShellExt;
|
||||
|
||||
@@ -39,25 +40,24 @@ fn spawn_and_monitor_sidecar(app_handle: tauri::AppHandle) -> Result<(), String>
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
let resource_path = app_handle.path().resolve("binaries/biliup.exe", BaseDirectory::Resource).map_err(|e1| e1.to_string())?;
|
||||
// Spawn sidecar
|
||||
let sidecar_command = app_handle
|
||||
.shell()
|
||||
.sidecar("biliup")
|
||||
.map_err(|e| e.to_string())?;
|
||||
.command(resource_path);
|
||||
// 获取当前可执行文件的路径
|
||||
let exe_path = env::current_exe().unwrap();
|
||||
// 获取可执行文件所在的目录
|
||||
let exe_dir = exe_path.parent().unwrap();
|
||||
println!("[tauri] Sidecar directory: {}", exe_dir.display());
|
||||
let (mut rx, child) = sidecar_command
|
||||
.args(["-P", "19159"])
|
||||
.current_dir(exe_dir)
|
||||
// .env("PYTHONUTF8", "1")
|
||||
.spawn()
|
||||
.map_err(|e| e.to_string())?;
|
||||
// Store the child process in the app state
|
||||
if let Some(state) = app_handle.try_state::<Arc<Mutex<Option<CommandChild>>>>() {
|
||||
println!("child {:?}", child);
|
||||
println!("pid {}", child.pid());
|
||||
*state.lock().unwrap() = Some(child);
|
||||
} else {
|
||||
@@ -104,52 +104,53 @@ fn shutdown_sidecar(app_handle: &tauri::AppHandle) -> Result<String, String> {
|
||||
.lock()
|
||||
.map_err(|_| "[tauri] Failed to acquire lock on sidecar process.")?;
|
||||
let shell = app_handle.shell();
|
||||
let kill_command;
|
||||
// let kill_command;
|
||||
if let Some(mut process) = child_process.take() {
|
||||
let pid = process.pid();
|
||||
process.kill().map_err(|e1| e1.to_string())?;
|
||||
println!("[tauri] Killing sidecar. Pid: {}", pid);
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// taskkill /F (强制) /T (杀死进程树) /PID <pid>
|
||||
// 这是在 Windows 上杀死进程及其所有子进程的最佳方式
|
||||
println!("Using 'taskkill' on Windows to force kill process tree.");
|
||||
kill_command = shell
|
||||
.command("taskkill")
|
||||
.args(&["/F", "/T", "/PID", &pid.to_string()])
|
||||
.spawn();
|
||||
}
|
||||
match kill_command {
|
||||
Ok((mut rx, _)) => {
|
||||
tauri::async_runtime::block_on(async move {
|
||||
let encoding = Encoding::for_label("GBK".as_ref()).unwrap();
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
CommandEvent::Stdout(line) => println!(
|
||||
"Force kill stdout: {:?}",
|
||||
encoding.decode_with_bom_removal(&line).0
|
||||
),
|
||||
CommandEvent::Stderr(line) => eprintln!(
|
||||
"Force kill stderr: {:?}",
|
||||
encoding.decode_with_bom_removal(&line).0
|
||||
),
|
||||
CommandEvent::Terminated(payload) => {
|
||||
if payload.code == Some(0) {
|
||||
println!("Sidecar (PID: {}) and its children force killed successfully.", pid);
|
||||
} else {
|
||||
// 在 Windows 上,如果进程已不存在,taskkill 会返回非0代码(如128),这是正常的。
|
||||
// 在 Linux/macOS 上,如果进程已不存在,kill/pkill 会报错,也是正常的。
|
||||
println!("Force kill command finished. Process was likely already dead.");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Failed to execute force kill command: {}", e);
|
||||
}
|
||||
}
|
||||
// #[cfg(target_os = "windows")]
|
||||
// {
|
||||
// // taskkill /F (强制) /T (杀死进程树) /PID <pid>
|
||||
// // 这是在 Windows 上杀死进程及其所有子进程的最佳方式
|
||||
// println!("Using 'taskkill' on Windows to force kill process tree.");
|
||||
// kill_command = shell
|
||||
// .command("taskkill")
|
||||
// .args(&["/F", "/T", "/PID", &pid.to_string()])
|
||||
// .spawn();
|
||||
// }
|
||||
// match kill_command {
|
||||
// Ok((mut rx, _)) => {
|
||||
// tauri::async_runtime::block_on(async move {
|
||||
// let encoding = Encoding::for_label("GBK".as_ref()).unwrap();
|
||||
// while let Some(event) = rx.recv().await {
|
||||
// match event {
|
||||
// CommandEvent::Stdout(line) => println!(
|
||||
// "Force kill stdout: {:?}",
|
||||
// encoding.decode_with_bom_removal(&line).0
|
||||
// ),
|
||||
// CommandEvent::Stderr(line) => eprintln!(
|
||||
// "Force kill stderr: {:?}",
|
||||
// encoding.decode_with_bom_removal(&line).0
|
||||
// ),
|
||||
// CommandEvent::Terminated(payload) => {
|
||||
// if payload.code == Some(0) {
|
||||
// println!("Sidecar (PID: {}) and its children force killed successfully.", pid);
|
||||
// } else {
|
||||
// // 在 Windows 上,如果进程已不存在,taskkill 会返回非0代码(如128),这是正常的。
|
||||
// // 在 Linux/macOS 上,如果进程已不存在,kill/pkill 会报错,也是正常的。
|
||||
// println!("Force kill command finished. Process was likely already dead.");
|
||||
// }
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// Err(e) => {
|
||||
// eprintln!("Failed to execute force kill command: {}", e);
|
||||
// }
|
||||
// }
|
||||
|
||||
println!("[tauri] Sent 'sidecar shutdown' command to sidecar.");
|
||||
Ok("'sidecar shutdown' command sent.".to_string())
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"externalBin": [
|
||||
"binaries/biliup"
|
||||
"resources": [
|
||||
"binaries/"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user