From 3698bcf28a01e4f4fb56d80104d581bc720965a8 Mon Sep 17 00:00:00 2001
From: ForgQi <34411314+ForgQi@users.noreply.github.com>
Date: Thu, 20 Nov 2025 03:26:40 +0800
Subject: [PATCH] Add struct-patch support and improve config handling
Introduces the struct-patch crate for partial config updates and applies it to configuration models. Refactors config access patterns, adds PartialEq to several types, and updates LiveStreamer to use ConfigPatch for override_cfg. Also updates the CI workflow to use macos-15-intel, improves download task cancellation logic, and upgrades @douyinfe/semi-ui and related dependencies in the frontend.
---
.github/workflows/ci.yml | 2 +-
Cargo.lock | 25 +-
Cargo.toml | 2 +-
app/(app)/streamers/page.tsx | 21 +-
crates/biliup-cli/Cargo.toml | 1 +
.../biliup-cli/src/server/common/download.rs | 16 +-
crates/biliup-cli/src/server/common/upload.rs | 2 +-
crates/biliup-cli/src/server/config.rs | 37 +-
.../biliup-cli/src/server/core/downloader.rs | 2 +-
crates/biliup-cli/src/server/core/monitor.rs | 16 +-
.../src/server/infrastructure/context.rs | 2 +-
.../server/infrastructure/models/hook_step.rs | 2 +-
.../infrastructure/models/live_streamer.rs | 11 +-
.../server/infrastructure/service_register.rs | 4 +-
crates/biliup/src/uploader/bilibili.rs | 2 +-
package-lock.json | 663 ++++++++++++++++--
package.json | 2 +-
17 files changed, 702 insertions(+), 108 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5d6e9f7..b0fc3c2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -162,7 +162,7 @@ jobs:
strategy:
matrix:
platform:
- - runner: macos-13
+ - runner: macos-15-intel
target: x86_64
- runner: macos-14
target: aarch64
diff --git a/Cargo.lock b/Cargo.lock
index be01054..a3b6348 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -362,7 +362,7 @@ dependencies = [
[[package]]
name = "biliup-cli"
-version = "1.1.23"
+version = "1.1.24"
dependencies = [
"async-channel",
"async-trait",
@@ -396,6 +396,7 @@ dependencies = [
"serde_urlencoded",
"serde_yaml",
"sqlx",
+ "struct-patch",
"tempfile",
"thiserror 2.0.17",
"time",
@@ -3644,7 +3645,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "stream-gears"
-version = "1.1.23"
+version = "1.1.24"
dependencies = [
"async-channel",
"async-trait",
@@ -3699,6 +3700,26 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+[[package]]
+name = "struct-patch"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e986d2cf6e819bd843319120453d837dfdfa31497c3fee4cefa614b2d182d8c"
+dependencies = [
+ "struct-patch-derive",
+]
+
+[[package]]
+name = "struct-patch-derive"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68c6387c1c7b53060605101b63d93edca618c6cf7ce61839f2ec2a527419fdb5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "structmeta"
version = "0.3.0"
diff --git a/Cargo.toml b/Cargo.toml
index 7dd853d..54ce84b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,7 @@ members = [
resolver = "3"
[workspace.package]
-version = "1.1.23"
+version = "1.1.24"
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
diff --git a/app/(app)/streamers/page.tsx b/app/(app)/streamers/page.tsx
index eacca58..39306dd 100644
--- a/app/(app)/streamers/page.tsx
+++ b/app/(app)/streamers/page.tsx
@@ -64,10 +64,7 @@ export default function Home() {
statusTag = 空闲
break
case 'Pending':
- statusTag = 等待下载
- break
- case 'Inspecting':
- statusTag = 检测/上传中
+ statusTag = 检测中
break
case 'OutOfSchedule':
statusTag = 非录播时间
@@ -271,18 +268,10 @@ export default function Home() {
>
}>
-
-
-
-
- }>
-
-
-
- } trigger="click" position="bottomRight">
- }>
-
+
+
+ }>
+
diff --git a/crates/biliup-cli/Cargo.toml b/crates/biliup-cli/Cargo.toml
index a2a78f3..9b2fc5e 100644
--- a/crates/biliup-cli/Cargo.toml
+++ b/crates/biliup-cli/Cargo.toml
@@ -59,6 +59,7 @@ async-channel = { version = "2.5.0" }
nom = "8"
tokio-util = "0.7.16"
libc = "0.2.174"
+struct-patch = "0.10.4"
[features]
default = []
python-bridge = []
diff --git a/crates/biliup-cli/src/server/common/download.rs b/crates/biliup-cli/src/server/common/download.rs
index a588733..4ee1ade 100644
--- a/crates/biliup-cli/src/server/common/download.rs
+++ b/crates/biliup-cli/src/server/common/download.rs
@@ -116,9 +116,7 @@ impl SegmentEventProcessor {
file_validator: FileValidator::new(
ctx.worker
.clone()
- .config
- .read()
- .unwrap()
+ .get_config()
.filtering_threshold
* 1000
* 1000,
@@ -217,6 +215,11 @@ impl DownloadTask {
.await;
info!("initialize_components completed: {url}");
+
+ if self.token.is_cancelled() {
+ info!(url = url, "task is cancelled");
+ break components;
+ }
ctx = ctx.clone();
// 检查流状态
match plugin.check_stream().await {
@@ -244,11 +247,6 @@ impl DownloadTask {
}
}
- if self.token.is_cancelled() {
- info!(url = url, "task is cancelled");
- break components;
- }
-
if retry_count >= max_retries {
warn!(
url = url,
@@ -284,8 +282,8 @@ impl DownloadTask {
// 清理资源
// 确保状态更新和资源清理
rooms_handle.wake_waker(worker.id()).await;
- self.done_notify.notify_one();
info!("Download task completed: {:?}", result);
+ self.done_notify.notify_one();
Ok(())
}
diff --git a/crates/biliup-cli/src/server/common/upload.rs b/crates/biliup-cli/src/server/common/upload.rs
index 9e1086b..3112416 100644
--- a/crates/biliup-cli/src/server/common/upload.rs
+++ b/crates/biliup-cli/src/server/common/upload.rs
@@ -66,7 +66,7 @@ where
recorder,
)
.await?;
- let submit_api = ctx.worker.config.read().unwrap().submit_api.clone();
+ let submit_api = ctx.worker.get_config().submit_api.clone();
submit_to_bilibili(&upload_context.bilibili, &studio, submit_api.as_deref()).await?;
}
diff --git a/crates/biliup-cli/src/server/config.rs b/crates/biliup-cli/src/server/config.rs
index d6a3c1f..96eeb12 100644
--- a/crates/biliup-cli/src/server/config.rs
+++ b/crates/biliup-cli/src/server/config.rs
@@ -5,9 +5,11 @@ use biliup::bilibili::Credit;
use error_stack::bail;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, path::Path, path::PathBuf};
+use struct_patch::Patch;
/// 全局配置结构体
-#[derive(bon::Builder, Debug, Clone, Serialize, Deserialize)]
+#[derive(bon::Builder, Debug, PartialEq, Clone, Serialize, Deserialize, Patch)]
+#[patch(attribute(derive(Debug, Clone, Default, Deserialize, Serialize)))]
pub struct Config {
// ===== 全局录播与上传设置 =====
/// 下载器类型:streamlink | ffmpeg | stream-gears | 自定义
@@ -216,7 +218,7 @@ pub struct Config {
}
/// 主播配置结构体
-#[derive(Debug, Clone, Serialize, Deserialize, Default)]
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
pub struct StreamerConfig {
/// 直播间URL列表
pub url: Vec,
@@ -310,7 +312,8 @@ pub struct StreamerConfig {
}
/// 用户配置结构体
-#[derive(bon::Builder, Debug, Clone, Serialize, Deserialize, Default)]
+#[derive(bon::Builder, PartialEq, Debug, Clone, Serialize, Deserialize, Default, Patch)]
+#[patch(attribute(derive(Debug, Default, Deserialize)))]
pub struct UserConfig {
// B站配置
/// B站Cookie字符串
@@ -417,3 +420,31 @@ impl Config {
)))
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // 情况 1: 字段存在且有字符串值
+ #[test]
+ fn test_field_with_value() {
+ let json = r#"{"maybe_name": "Alice"}"#;
+
+ let patch = r#"{"maybe_name": "Alice"}"#;
+
+ // Single Option: Some("Alice")
+ let mut single: Config = serde_json::from_str(json).unwrap();
+
+ let patch: ConfigPatch = serde_json::from_str(json).unwrap();
+
+ single.apply(patch);
+
+ assert_eq!(
+ single,
+ Config::builder()
+ .streamers(Default::default())
+ .build(),
+ "普通Option正常包裹一层"
+ );
+ }
+}
\ No newline at end of file
diff --git a/crates/biliup-cli/src/server/core/downloader.rs b/crates/biliup-cli/src/server/core/downloader.rs
index 3ed18c5..73c07ed 100644
--- a/crates/biliup-cli/src/server/core/downloader.rs
+++ b/crates/biliup-cli/src/server/core/downloader.rs
@@ -49,7 +49,7 @@ impl DownloadConfig {
/// 下载器类型枚举
/// 定义支持的各种下载器类型
-#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
+#[derive(PartialEq, Debug, Clone, Copy, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum DownloaderType {
/// Ytarchive下载器
diff --git a/crates/biliup-cli/src/server/core/monitor.rs b/crates/biliup-cli/src/server/core/monitor.rs
index dbb9ac1..4b97dcb 100644
--- a/crates/biliup-cli/src/server/core/monitor.rs
+++ b/crates/biliup-cli/src/server/core/monitor.rs
@@ -197,7 +197,9 @@ impl Monitor {
// 忽略发送错误。如果发送失败,下面的recv.await也会失败
// 没有必要检查两次失败
let _ = self.sender.send(msg).await;
- recv.await.expect("Actor task has been killed")
+ if let Some(worker) = recv.await.expect("Actor task has been killed") {
+ worker.change_status(Stage::Download, WorkerStatus::Idle).await;
+ }
}
/// 删除指定ID的工作器
@@ -344,7 +346,7 @@ enum ActorMessage {
AddPlugin(oneshot::Sender<()>, Arc),
/// 删除工作器
Del {
- respond_to: oneshot::Sender<()>,
+ respond_to: oneshot::Sender