#[macro_use] extern crate tracing; mod adapter; mod api; mod bilibili; mod config; mod database; mod downloader; mod error; mod notifier; mod task; mod utils; mod workflow; use std::collections::VecDeque; use std::fmt::Debug; use std::future::Future; use std::sync::Arc; use bilibili::BiliClient; use parking_lot::RwLock; use sea_orm::DatabaseConnection; use task::{http_server, video_downloader}; use tokio_util::sync::CancellationToken; use tokio_util::task::TaskTracker; use crate::api::{LogHelper, MAX_HISTORY_LOGS}; use crate::config::{ARGS, CONFIG_DIR, VersionedConfig}; use crate::database::setup_database; use crate::utils::init_logger; use crate::utils::signal::terminate; #[tokio::main] async fn main() { let (connection, log_writer) = init().await; let bili_client = Arc::new(BiliClient::new()); let token = CancellationToken::new(); let tracker = TaskTracker::new(); spawn_task( "HTTP 服务", http_server(connection.clone(), bili_client.clone(), log_writer), &tracker, token.clone(), ); spawn_task( "定时下载", video_downloader(connection.clone(), bili_client), &tracker, token.clone(), ); tracker.close(); handle_shutdown(connection, tracker, token).await } fn spawn_task( task_name: &'static str, task: impl Future + Send + 'static, tracker: &TaskTracker, token: CancellationToken, ) { tracker.spawn(async move { tokio::select! { res = task => { error!("「{}」异常结束,返回结果为:「{:?}」,取消其它仍在执行的任务..", task_name, res); token.cancel(); }, _ = token.cancelled() => { info!("「{}」接收到取消信号,终止运行..", task_name); } } }); } /// 初始化日志系统、打印欢迎信息,初始化数据库连接和全局配置 async fn init() -> (DatabaseConnection, LogHelper) { let (tx, _rx) = tokio::sync::broadcast::channel(30); let log_history = Arc::new(RwLock::new(VecDeque::with_capacity(MAX_HISTORY_LOGS + 1))); let log_writer = LogHelper::new(tx, log_history.clone()); init_logger(&ARGS.log_level, Some(log_writer.clone())); info!("欢迎使用 Bili-Sync,当前程序版本:{}", config::version()); info!("项目地址:https://github.com/amtoaer/bili-sync"); let connection = setup_database(&CONFIG_DIR.join("data.sqlite")) .await .expect("数据库初始化失败"); info!("数据库初始化完成"); VersionedConfig::init(&connection).await.expect("配置初始化失败"); info!("配置初始化完成"); (connection, log_writer) } async fn handle_shutdown(connection: DatabaseConnection, tracker: TaskTracker, token: CancellationToken) { tokio::select! { _ = tracker.wait() => { error!("所有任务均已终止..") } _ = terminate() => { info!("接收到终止信号,开始终止任务.."); token.cancel(); tracker.wait().await; info!("所有任务均已终止.."); } } info!("正在关闭数据库连接.."); match connection.close().await { Ok(()) => info!("数据库连接已关闭,程序结束"), Err(e) => error!("关闭数据库连接时遇到错误:{:#},程序异常结束", e), } }