mirror of
https://github.com/Xinrea/bili-shadowreplay.git
synced 2025-11-24 20:15:34 +08:00
ci/cd: docker package build
This commit is contained in:
39
.dockerignore
Normal file
39
.dockerignore
Normal file
@@ -0,0 +1,39 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
.pnpm-store
|
||||
.npm
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
|
||||
# Build outputs
|
||||
dist
|
||||
build
|
||||
target
|
||||
*.log
|
||||
|
||||
# Version control
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# IDE and editor files
|
||||
.idea
|
||||
.vscode
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Debug files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Tauri specific
|
||||
src-tauri/target
|
||||
src-tauri/dist
|
||||
50
.github/workflows/package.yml
vendored
Normal file
50
.github/workflows/package.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
name: Docker Build and Push
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=sha,format=long
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
70
Dockerfile
Normal file
70
Dockerfile
Normal file
@@ -0,0 +1,70 @@
|
||||
# Build frontend
|
||||
FROM node:20-bullseye AS frontend-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy package files
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
# Install dependencies with specific flags
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
# Copy source files
|
||||
COPY . .
|
||||
|
||||
# Build frontend
|
||||
RUN yarn build
|
||||
|
||||
# Build Rust backend
|
||||
FROM rust:1.85-slim AS rust-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install required system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
cmake \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
libwebkit2gtk-4.1-dev \
|
||||
libappindicator3-dev \
|
||||
librsvg2-dev \
|
||||
patchelf \
|
||||
libclang-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy Rust project files
|
||||
COPY src-tauri/Cargo.toml src-tauri/Cargo.lock ./src-tauri/
|
||||
COPY src-tauri/src ./src-tauri/src
|
||||
|
||||
# Build Rust backend
|
||||
WORKDIR /app/src-tauri
|
||||
RUN cargo build --features headless --release
|
||||
|
||||
# Final stage
|
||||
FROM debian:bullseye-slim AS final
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
libssl1.1 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy built frontend
|
||||
COPY --from=frontend-builder /app/dist ./dist
|
||||
|
||||
# Copy built Rust binary
|
||||
COPY --from=rust-builder /app/src-tauri/target/release/bili-shadowreplay .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Run the application
|
||||
CMD ["./bili-shadowreplay"]
|
||||
@@ -6,9 +6,6 @@
|
||||

|
||||

|
||||
|
||||
> [!WARNING]
|
||||
> v2.0.0 版本为重大更新,将不兼容 v1.x 版本的数据。
|
||||
|
||||
BiliBili ShadowReplay 是一个缓存直播并进行实时编辑投稿的工具。通过划定时间区间,并编辑简单的必需信息,即可完成直播切片以及投稿,将整个流程压缩到分钟级。同时,也支持对缓存的历史直播进行回放,以及相同的切片编辑投稿处理流程。
|
||||
|
||||
目前仅支持 B 站和抖音平台的直播。
|
||||
|
||||
3
src-tauri/.gitignore
vendored
3
src-tauri/.gitignore
vendored
@@ -3,4 +3,5 @@
|
||||
/target/
|
||||
tmps
|
||||
clips
|
||||
data
|
||||
data
|
||||
config.toml
|
||||
791
src-tauri/Cargo.lock
generated
791
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -9,11 +9,7 @@ edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = ["protocol-asset", "tray-icon"] }
|
||||
serde_json = "1.0"
|
||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||
serde_derive = "1.0.158"
|
||||
@@ -37,27 +33,19 @@ urlencoding = "2.1.3"
|
||||
log = "0.4.22"
|
||||
simplelog = "0.12.2"
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite"] }
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-http = "2"
|
||||
tauri-utils = "2"
|
||||
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
|
||||
tauri-plugin-os = "2"
|
||||
tauri-plugin-notification = "2"
|
||||
rand = "0.8.5"
|
||||
base64 = "0.21"
|
||||
mime_guess = "2.0"
|
||||
async-trait = "0.1.87"
|
||||
whisper-rs = "0.14.2"
|
||||
hound = "3.5.1"
|
||||
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs" }
|
||||
uuid = { version = "1.4", features = ["v4"] }
|
||||
axum = { version = "0.7", features = ["macros"] }
|
||||
tower-http = { version = "0.5", features = ["cors", "fs", "limit"] }
|
||||
futures-core = "0.3"
|
||||
futures = "0.3"
|
||||
tokio-util = { version = "0.7", features = ["io"] }
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem
|
||||
@@ -65,7 +53,6 @@ tokio-util = { version = "0.7", features = ["io"] }
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
cuda = ["whisper-rs/cuda"]
|
||||
headless = []
|
||||
default = ["headless"]
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-single-instance = "2"
|
||||
@@ -76,3 +63,18 @@ whisper-rs = { version = "0.14.2", default-features = false }
|
||||
[target.'cfg(darwin)'.dependencies.whisper-rs]
|
||||
version = "0.14.2"
|
||||
features = ["metal"]
|
||||
|
||||
[target.'cfg(not(feature = "headless"))'.dependencies]
|
||||
tauri = { version = "2", features = ["protocol-asset", "tray-icon"] }
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-http = "2"
|
||||
tauri-utils = "2"
|
||||
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
|
||||
tauri-plugin-os = "2"
|
||||
tauri-plugin-notification = "2"
|
||||
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs" }
|
||||
|
||||
[target.'cfg(not(feature = "headless"))'.build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
14
src-tauri/config.example.toml
Normal file
14
src-tauri/config.example.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
cache = "./cache"
|
||||
output = "./output"
|
||||
live_start_notify = true
|
||||
live_end_notify = true
|
||||
clip_notify = true
|
||||
post_notify = true
|
||||
auto_subtitle = false
|
||||
whisper_model = ""
|
||||
whisper_prompt = "这是一段中文 你们好"
|
||||
clip_name_format = "[{room_id}][{live_id}][{title}][{created_at}].mp4"
|
||||
|
||||
[auto_generate]
|
||||
enabled = false
|
||||
encode_danmu = false
|
||||
File diff suppressed because one or more lines are too long
@@ -2372,6 +2372,12 @@
|
||||
"const": "core:app:allow-set-app-theme",
|
||||
"markdownDescription": "Enables the set_app_theme command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the set_dock_visibility command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-set-dock-visibility",
|
||||
"markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the tauri_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -2432,6 +2438,12 @@
|
||||
"const": "core:app:deny-set-app-theme",
|
||||
"markdownDescription": "Denies the set_app_theme command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the set_dock_visibility command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-set-dock-visibility",
|
||||
"markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the tauri_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -2372,6 +2372,12 @@
|
||||
"const": "core:app:allow-set-app-theme",
|
||||
"markdownDescription": "Enables the set_app_theme command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the set_dock_visibility command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-set-dock-visibility",
|
||||
"markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the tauri_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -2432,6 +2438,12 @@
|
||||
"const": "core:app:deny-set-app-theme",
|
||||
"markdownDescription": "Denies the set_app_theme command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the set_dock_visibility command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-set-dock-visibility",
|
||||
"markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the tauri_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
@@ -10,7 +10,9 @@ use crate::{recorder::PlatformType, recorder_manager::ClipRangeParams};
|
||||
pub struct Config {
|
||||
pub cache: String,
|
||||
pub output: String,
|
||||
#[serde(skip)]
|
||||
pub webid: String,
|
||||
#[serde(skip)]
|
||||
pub webid_ts: i64,
|
||||
pub live_start_notify: bool,
|
||||
pub live_end_notify: bool,
|
||||
@@ -26,6 +28,8 @@ pub struct Config {
|
||||
pub clip_name_format: String,
|
||||
#[serde(default = "default_auto_generate_config")]
|
||||
pub auto_generate: AutoGenerateConfig,
|
||||
#[serde(skip)]
|
||||
pub config_path: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
@@ -58,9 +62,7 @@ fn default_auto_generate_config() -> AutoGenerateConfig {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load() -> Self {
|
||||
let app_dirs = AppDirs::new(Some("cn.vjoi.bili-shadowreplay"), false).unwrap();
|
||||
let config_path = app_dirs.config_dir.join("Conf.toml");
|
||||
pub fn load(config_path: &str) -> Self {
|
||||
if let Ok(content) = std::fs::read_to_string(config_path) {
|
||||
if let Ok(config) = toml::from_str(&content) {
|
||||
return config;
|
||||
@@ -69,18 +71,8 @@ impl Config {
|
||||
let config = Config {
|
||||
webid: "".to_string(),
|
||||
webid_ts: 0,
|
||||
cache: app_dirs
|
||||
.cache_dir
|
||||
.join("cache")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
output: app_dirs
|
||||
.data_dir
|
||||
.join("output")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
cache: "./cache".to_string(),
|
||||
output: "./output".to_string(),
|
||||
live_start_notify: true,
|
||||
live_end_notify: true,
|
||||
clip_notify: true,
|
||||
@@ -90,6 +82,7 @@ impl Config {
|
||||
whisper_prompt: "这是一段中文 你们好".to_string(),
|
||||
clip_name_format: "[{room_id}][{live_id}][{title}][{created_at}].mp4".to_string(),
|
||||
auto_generate: default_auto_generate_config(),
|
||||
config_path: config_path.to_string(),
|
||||
};
|
||||
config.save();
|
||||
config
|
||||
@@ -97,11 +90,7 @@ impl Config {
|
||||
|
||||
pub fn save(&self) {
|
||||
let content = toml::to_string(&self).unwrap();
|
||||
let app_dirs = AppDirs::new(Some("cn.vjoi.bili-shadowreplay"), false).unwrap();
|
||||
// Create app dirs if not exists
|
||||
std::fs::create_dir_all(&app_dirs.config_dir).unwrap();
|
||||
let config_path = app_dirs.config_dir.join("Conf.toml");
|
||||
std::fs::write(config_path, content).unwrap();
|
||||
std::fs::write(self.config_path.clone(), content).unwrap();
|
||||
}
|
||||
|
||||
pub fn set_cache_path(&mut self, path: &str) {
|
||||
|
||||
@@ -1031,7 +1031,7 @@ pub async fn start_api_server(state: State) {
|
||||
|
||||
let app = Router::new()
|
||||
// Serve static files from dist directory
|
||||
.nest_service("/", ServeDir::new("../dist"))
|
||||
.nest_service("/", ServeDir::new("./dist"))
|
||||
// Account commands
|
||||
.route("/api/get_accounts", post(handler_get_accounts))
|
||||
.route("/api/add_account", post(handler_add_account))
|
||||
|
||||
@@ -18,6 +18,7 @@ mod subtitle_generator;
|
||||
mod tray;
|
||||
|
||||
use archive_migration::try_rebuild_archives;
|
||||
use clap::{arg, command, Parser};
|
||||
use config::Config;
|
||||
use database::Database;
|
||||
use futures_core::future::BoxFuture;
|
||||
@@ -129,23 +130,27 @@ impl MigrationSource<'static> for MigrationList {
|
||||
}
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
async fn setup_server_state() -> Result<State, Box<dyn std::error::Error>> {
|
||||
async fn setup_server_state(args: Args) -> Result<State, Box<dyn std::error::Error>> {
|
||||
use progress_manager::ProgressManager;
|
||||
use progress_reporter::EventEmitter;
|
||||
|
||||
setup_logging(Path::new("bsr.log")).await?;
|
||||
setup_logging(Path::new("./")).await?;
|
||||
println!("Setting up server state...");
|
||||
let client = Arc::new(BiliClient::new()?);
|
||||
let config = Arc::new(RwLock::new(Config::load()));
|
||||
let config = Arc::new(RwLock::new(Config::load(&args.config)));
|
||||
let db = Arc::new(Database::new());
|
||||
// connect to sqlite database
|
||||
|
||||
let conn_url = "sqlite:data/data_v2.db";
|
||||
|
||||
if !Sqlite::database_exists(conn_url).await.unwrap_or(false) {
|
||||
Sqlite::create_database(conn_url).await?;
|
||||
let conn_url = format!("sqlite:{}/data_v2.db", args.db);
|
||||
// create db folder if not exists
|
||||
if !Path::new(&args.db).exists() {
|
||||
std::fs::create_dir_all(&args.db)?;
|
||||
}
|
||||
let db_pool: Pool<Sqlite> = Pool::connect(conn_url).await?;
|
||||
|
||||
if !Sqlite::database_exists(&conn_url).await.unwrap_or(false) {
|
||||
Sqlite::create_database(&conn_url).await?;
|
||||
}
|
||||
let db_pool: Pool<Sqlite> = Pool::connect(&conn_url).await?;
|
||||
let migrations = get_migrations();
|
||||
|
||||
let migrator = Migrator::new(MigrationList(migrations))
|
||||
@@ -174,11 +179,16 @@ async fn setup_server_state() -> Result<State, Box<dyn std::error::Error>> {
|
||||
|
||||
#[cfg(not(feature = "headless"))]
|
||||
async fn setup_app_state(app: &tauri::App) -> Result<State, Box<dyn std::error::Error>> {
|
||||
use platform_dirs::AppDirs;
|
||||
use progress_manager::ProgressManager;
|
||||
use progress_reporter::EventEmitter;
|
||||
|
||||
println!("Setting up app state...");
|
||||
let app_dirs = AppDirs::new(Some("cn.vjoi.bili-shadowreplay"), false).unwrap();
|
||||
let config_path = app_dirs.config_dir.join("Conf.toml");
|
||||
|
||||
let client = Arc::new(BiliClient::new()?);
|
||||
let config = Arc::new(RwLock::new(Config::load()));
|
||||
let config = Arc::new(RwLock::new(Config::load(config_path.to_str().unwrap())));
|
||||
let config_clone = config.clone();
|
||||
let dbs = app.state::<tauri_plugin_sql::DbInstances>().inner();
|
||||
let db = Arc::new(Database::new());
|
||||
@@ -188,8 +198,11 @@ async fn setup_app_state(app: &tauri::App) -> Result<State, Box<dyn std::error::
|
||||
let log_dir = app.path().app_log_dir()?;
|
||||
setup_logging(&log_dir).await?;
|
||||
|
||||
let emitter = EventEmitter::new(app.handle().clone());
|
||||
|
||||
let recorder_manager = Arc::new(RecorderManager::new(
|
||||
app.handle().clone(),
|
||||
app.app_handle().clone(),
|
||||
emitter,
|
||||
db.clone(),
|
||||
config.clone(),
|
||||
));
|
||||
@@ -407,10 +420,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to the config file
|
||||
#[arg(short, long, default_value_t = String::from("config.toml"))]
|
||||
config: String,
|
||||
|
||||
/// Path to the database folder
|
||||
#[arg(short, long, default_value_t = String::from("./data"))]
|
||||
db: String,
|
||||
}
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let state = setup_server_state()
|
||||
// get params from command line
|
||||
let args = Args::parse();
|
||||
let state = setup_server_state(args)
|
||||
.await
|
||||
.expect("Failed to setup server state");
|
||||
http_server::start_api_server(state).await;
|
||||
|
||||
@@ -2,13 +2,16 @@ use async_trait::async_trait;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::sync::LazyLock;
|
||||
use tauri::AppHandle;
|
||||
use tauri::Emitter;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::progress_manager::Event;
|
||||
use crate::recorder::danmu::DanmuEntry;
|
||||
|
||||
#[cfg(not(feature = "headless"))]
|
||||
use {
|
||||
crate::recorder::danmu::DanmuEntry,
|
||||
tauri::{AppHandle, Emitter},
|
||||
};
|
||||
|
||||
type CancelFlagMap = std::collections::HashMap<String, Arc<AtomicBool>>;
|
||||
|
||||
@@ -49,7 +52,7 @@ impl EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit(&self, event: Event) {
|
||||
pub fn emit(&self, event: &Event) {
|
||||
#[cfg(not(feature = "headless"))]
|
||||
{
|
||||
match event {
|
||||
@@ -65,7 +68,13 @@ impl EventEmitter {
|
||||
}
|
||||
Event::DanmuReceived { room, ts, content } => {
|
||||
self.app_handle
|
||||
.emit(&format!("danmu:{}", room), DanmuEntry { ts, content })
|
||||
.emit(
|
||||
&format!("danmu:{}", room),
|
||||
DanmuEntry {
|
||||
ts: *ts,
|
||||
content: content.clone(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
_ => {}
|
||||
@@ -73,7 +82,7 @@ impl EventEmitter {
|
||||
}
|
||||
|
||||
#[cfg(feature = "headless")]
|
||||
let _ = self.sender.send(event);
|
||||
let _ = self.sender.send(event.clone());
|
||||
}
|
||||
}
|
||||
impl ProgressReporter {
|
||||
@@ -81,7 +90,7 @@ impl ProgressReporter {
|
||||
// if already exists, return
|
||||
if CANCEL_FLAG_MAP.read().await.get(event_id).is_some() {
|
||||
log::error!("Task already exists: {}", event_id);
|
||||
emitter.emit(Event::ProgressFinished {
|
||||
emitter.emit(&Event::ProgressFinished {
|
||||
id: event_id.to_string(),
|
||||
success: false,
|
||||
message: "任务已经存在".to_string(),
|
||||
@@ -106,14 +115,14 @@ impl ProgressReporter {
|
||||
#[async_trait]
|
||||
impl ProgressReporterTrait for ProgressReporter {
|
||||
fn update(&self, content: &str) {
|
||||
self.emitter.emit(Event::ProgressUpdate {
|
||||
self.emitter.emit(&Event::ProgressUpdate {
|
||||
id: self.event_id.clone(),
|
||||
content: content.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
async fn finish(&self, success: bool, message: &str) {
|
||||
self.emitter.emit(Event::ProgressFinished {
|
||||
self.emitter.emit(&Event::ProgressFinished {
|
||||
id: self.event_id.clone(),
|
||||
success,
|
||||
message: message.to_string(),
|
||||
|
||||
@@ -403,7 +403,7 @@ impl BiliRecorder {
|
||||
break;
|
||||
}
|
||||
if let WsStreamMessageType::DanmuMsg(msg) = msg {
|
||||
self.emitter.emit(Event::DanmuReceived {
|
||||
self.emitter.emit(&Event::DanmuReceived {
|
||||
room: self.room_id,
|
||||
ts: msg.timestamp as i64,
|
||||
content: msg.msg.clone(),
|
||||
|
||||
@@ -21,6 +21,7 @@ use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use tauri::AppHandle;
|
||||
use tokio::fs::{remove_file, write};
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::RwLock;
|
||||
@@ -58,6 +59,8 @@ pub enum RecorderEvent {
|
||||
}
|
||||
|
||||
pub struct RecorderManager {
|
||||
#[cfg(not(feature = "headless"))]
|
||||
app_handle: AppHandle,
|
||||
emitter: EventEmitter,
|
||||
db: Arc<Database>,
|
||||
config: Arc<RwLock<Config>>,
|
||||
@@ -104,12 +107,15 @@ impl From<RecorderManagerError> for String {
|
||||
|
||||
impl RecorderManager {
|
||||
pub fn new(
|
||||
#[cfg(not(feature = "headless"))] app_handle: AppHandle,
|
||||
emitter: EventEmitter,
|
||||
db: Arc<Database>,
|
||||
config: Arc<RwLock<Config>>,
|
||||
) -> RecorderManager {
|
||||
let (event_tx, _) = broadcast::channel(100);
|
||||
let manager = RecorderManager {
|
||||
#[cfg(not(feature = "headless"))]
|
||||
app_handle,
|
||||
emitter,
|
||||
db,
|
||||
config,
|
||||
@@ -134,6 +140,8 @@ impl RecorderManager {
|
||||
|
||||
pub fn clone(&self) -> Self {
|
||||
RecorderManager {
|
||||
#[cfg(not(feature = "headless"))]
|
||||
app_handle: self.app_handle.clone(),
|
||||
emitter: self.emitter.clone(),
|
||||
db: self.db.clone(),
|
||||
config: self.config.clone(),
|
||||
|
||||
Reference in New Issue
Block a user