feat: 给日志和错误信息补充丰富的SpanTrace

This commit is contained in:
lanyeeee
2026-03-04 04:59:48 +08:00
parent b5db04f5bd
commit 32c175ec2e
28 changed files with 519 additions and 202 deletions
+37 -4
View File
@@ -15,6 +15,7 @@ use tauri::{
http::{HeaderMap, HeaderValue},
};
use tokio::task::JoinSet;
use tracing::{Instrument, instrument};
use crate::{
config::ProxyMode,
@@ -74,6 +75,7 @@ impl BiliClient {
*self.content_length_client.write() = content_length_client;
}
#[instrument(level = "error", skip_all)]
pub async fn generate_qrcode(&self) -> eyre::Result<QrcodeData> {
// 发送生成二维码请求
let request = self
@@ -106,6 +108,7 @@ impl BiliClient {
Ok(qrcode_data)
}
#[instrument(level = "error", skip_all)]
pub async fn get_qrcode_status(&self, qrcode_key: &str) -> eyre::Result<QrcodeStatus> {
// 发送获取二维码状态请求
let params = json!({"qrcode_key": qrcode_key});
@@ -142,6 +145,7 @@ impl BiliClient {
Ok(qrcode_status)
}
#[instrument(level = "error", skip_all)]
pub async fn get_user_info(&self, sessdata: &str) -> eyre::Result<UserInfo> {
// 发送获取用户信息的请求
let request = self
@@ -177,6 +181,7 @@ impl BiliClient {
Ok(user_info)
}
#[instrument(level = "error", skip_all, fields(bvid = params.get_bvid(), aid = params.get_aid()))]
pub async fn get_normal_info(&self, params: GetNormalInfoParams) -> eyre::Result<NormalInfo> {
use GetNormalInfoParams::{Aid, Bvid};
let params = match params {
@@ -216,6 +221,7 @@ impl BiliClient {
Ok(normal_info)
}
#[instrument(level = "error", skip_all, fields(ep_id = params.get_ep_id(), season_id = params.get_season_id()))]
pub async fn get_bangumi_info(
&self,
params: GetBangumiInfoParams,
@@ -258,6 +264,7 @@ impl BiliClient {
Ok(bangumi_info)
}
#[instrument(level = "error", skip_all, fields(ep_id = params.get_ep_id(), season_id = params.get_season_id()))]
pub async fn get_cheese_info(&self, params: GetCheeseInfoParams) -> eyre::Result<CheeseInfo> {
use GetCheeseInfoParams::{EpId, SeasonId};
let params = match params {
@@ -297,6 +304,7 @@ impl BiliClient {
Ok(cheese_info)
}
#[instrument(level = "error", skip_all)]
pub async fn get_user_video_info(
&self,
params: GetUserVideoInfoParams,
@@ -358,6 +366,7 @@ impl BiliClient {
Ok(user_video_info)
}
#[instrument(level = "error", skip_all, fields(bvid = bvid, cid = cid))]
pub async fn get_normal_url(&self, bvid: &str, cid: i64) -> eyre::Result<NormalMediaUrl> {
let params = json!({
"bvid": bvid,
@@ -398,6 +407,7 @@ impl BiliClient {
Ok(media_url)
}
#[instrument(level = "error", skip_all, fields(cid = cid))]
pub async fn get_bangumi_url(&self, cid: i64) -> eyre::Result<BangumiMediaUrl> {
let media_url_v2 = self.get_bangumi_url_v2(cid).await?;
if media_url_v2.video_info.is_drm {
@@ -407,6 +417,7 @@ impl BiliClient {
}
}
#[instrument(level = "error", skip_all, fields(cid = cid))]
async fn get_bangumi_url_v1(&self, cid: i64) -> eyre::Result<BangumiMediaUrl> {
let params = json!({
"cid": cid,
@@ -449,6 +460,7 @@ impl BiliClient {
Ok(media_url)
}
#[instrument(level = "error", skip_all, fields(cid = cid))]
async fn get_bangumi_url_v2(&self, cid: i64) -> eyre::Result<BangumiMediaUrlV2> {
let params = json!({
"cid": cid,
@@ -492,6 +504,7 @@ impl BiliClient {
Ok(media_url)
}
#[instrument(level = "error", skip_all, fields(ep_id = ep_id))]
pub async fn get_cheese_url(&self, ep_id: i64) -> eyre::Result<CheeseMediaUrl> {
let params = json!({
"ep_id": ep_id,
@@ -534,6 +547,7 @@ impl BiliClient {
Ok(media_url)
}
#[instrument(level = "error", skip_all, fields(aid = aid, cid = cid))]
pub async fn get_player_info(&self, aid: i64, cid: i64) -> eyre::Result<PlayerInfo> {
let params = json!({
"aid": aid,
@@ -572,6 +586,7 @@ impl BiliClient {
Ok(player_info)
}
#[instrument(level = "error", skip_all)]
pub async fn get_fav_folders(&self, uid: i64) -> eyre::Result<FavFolders> {
let params = json!({"up_mid": uid});
// 发送获取收藏夹信息的请求
@@ -607,6 +622,7 @@ impl BiliClient {
Ok(fav_folders)
}
#[instrument(level = "error", skip_all)]
pub async fn get_fav_info(&self, params: GetFavInfoParams) -> eyre::Result<FavInfo> {
let params = json!({
"media_id": params.media_list_id,
@@ -647,6 +663,7 @@ impl BiliClient {
Ok(fav_info)
}
#[instrument(level = "error", skip_all)]
pub async fn get_watch_later_info(&self, page: i32) -> eyre::Result<WatchLaterInfo> {
// 发送获取稍后观看信息的请求
let params = json!({"ps": 20, "pn": page});
@@ -682,6 +699,7 @@ impl BiliClient {
Ok(watch_later_info)
}
#[instrument(level = "error", skip_all)]
pub async fn get_bangumi_follow_info(
&self,
params: GetBangumiFollowInfoParams,
@@ -726,6 +744,7 @@ impl BiliClient {
Ok(bangumi_follow_info)
}
#[instrument(level = "error", skip_all)]
pub async fn get_history_info(
&self,
params: GetHistoryInfoParams,
@@ -773,6 +792,7 @@ impl BiliClient {
Ok(history_info)
}
#[instrument(level = "error", skip_all)]
pub async fn get_media_chunk(
&self,
media_url: &str,
@@ -796,7 +816,9 @@ impl BiliClient {
Ok(bytes)
}
#[instrument(level = "error", skip_all, fields(media_url = media_url))]
pub async fn get_content_length(&self, media_url: &str) -> eyre::Result<u64> {
#[instrument(level = "error", skip_all)]
fn parse_content_length(headers: &HeaderMap) -> eyre::Result<u64> {
headers
.get("Content-Length")
@@ -807,6 +829,7 @@ impl BiliClient {
.wrap_err("Content-Length 响应头无法转换为整数")
}
#[instrument(level = "error", skip_all)]
fn parse_total_from_content_range(headers: &HeaderMap) -> eyre::Result<u64> {
// Example: "bytes 0-0/12345"
let content_range = headers
@@ -854,19 +877,21 @@ impl BiliClient {
Err(eyre!("预料之外的状态码({status})"))
}
#[instrument(level = "error", skip_all)]
pub async fn get_url_with_content_length(&self, urls: Vec<String>) -> Vec<(String, u64)> {
let mut url_with_content_length = Vec::new();
let mut join_set = JoinSet::new();
for url in urls {
let app = self.app.clone();
join_set.spawn(async move {
let get_content_length_task = async move {
let bili_client = app.get_bili_client();
let Ok(content_length) = bili_client.get_content_length(&url).await else {
return None;
};
Some((url, content_length))
});
};
join_set.spawn(get_content_length_task.in_current_span());
}
while let Some(join_result) = join_set.join_next().await {
@@ -880,6 +905,7 @@ impl BiliClient {
url_with_content_length
}
#[instrument(level = "error", skip_all, fields(aid = aid, cid = cid, duration = duration))]
pub async fn get_danmaku(
&self,
aid: i64,
@@ -895,7 +921,9 @@ impl BiliClient {
let client = client.clone();
let cookie = self.get_cookie();
join_set.spawn(async move {
let segment_span =
tracing::error_span!("get_danmaku_segment", segment_index = segment_index);
let segment_task = async move {
// 发送获取分段弹幕的请求
let params = json!({
"type": 1,
@@ -919,7 +947,8 @@ impl BiliClient {
DmSegMobileReply::decode(body).wrap_err("将body解析为DmSegMobileReply失败")?;
Ok(reply)
});
};
join_set.spawn(segment_task.instrument(segment_span));
}
let mut replies = Vec::new();
@@ -935,6 +964,7 @@ impl BiliClient {
Ok(replies)
}
#[instrument(level = "error", skip_all, fields(url = url))]
pub async fn get_subtitle(&self, url: &str) -> eyre::Result<Subtitle> {
let request = self.api_client.read().get(url);
let http_resp = request.send().await?;
@@ -950,6 +980,7 @@ impl BiliClient {
Ok(subtitle)
}
#[instrument(level = "error", skip_all, fields(url = url))]
pub async fn get_cover_data_and_ext(&self, url: &str) -> eyre::Result<(Bytes, String)> {
let request = self.api_client.read().get(url);
let http_resp = request.send().await?;
@@ -980,6 +1011,7 @@ impl BiliClient {
Ok((bytes, ext.to_string()))
}
#[instrument(level = "error", skip_all, fields(aid = aid))]
pub async fn get_tags(&self, aid: i64) -> eyre::Result<Tags> {
// 发送获取普通视频标签的请求
let params = json!({"aid": aid});
@@ -1015,6 +1047,7 @@ impl BiliClient {
Ok(tags)
}
#[instrument(level = "error", skip_all, fields(bvid = bvid, cid = cid))]
pub async fn get_skip_segments(
&self,
bvid: &str,
+27 -1
View File
@@ -2,6 +2,7 @@ use eyre::WrapErr;
use parking_lot::RwLock;
use tauri::AppHandle;
use tauri_plugin_opener::OpenerExt;
use tracing::instrument;
use crate::{
config::Config,
@@ -43,6 +44,8 @@ use crate::{
#[tauri::command(async)]
#[specta::specta]
#[allow(clippy::needless_pass_by_value)]
#[instrument(level = "error", skip_all)]
// TODO: 改成 app.get_config_manager().read().clone()
pub fn get_config(config: tauri::State<RwLock<Config>>) -> Config {
config.read().clone()
}
@@ -50,6 +53,7 @@ pub fn get_config(config: tauri::State<RwLock<Config>>) -> Config {
#[tauri::command(async)]
#[specta::specta]
#[allow(clippy::needless_pass_by_value)]
#[instrument(level = "error", skip_all)]
pub fn save_config(app: AppHandle, config: Config) -> CommandResult<()> {
let bili_client = app.get_bili_client();
let config_state = app.get_config();
@@ -94,6 +98,7 @@ pub fn save_config(app: AppHandle, config: Config) -> CommandResult<()> {
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn generate_qrcode(app: AppHandle) -> CommandResult<QrcodeData> {
let bili_client = app.get_bili_client();
let qrcode_data = bili_client
@@ -106,6 +111,7 @@ pub async fn generate_qrcode(app: AppHandle) -> CommandResult<QrcodeData> {
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn get_qrcode_status(app: AppHandle, qrcode_key: String) -> CommandResult<QrcodeStatus> {
let bili_client = app.get_bili_client();
let qrcode_status = bili_client
@@ -117,6 +123,7 @@ pub async fn get_qrcode_status(app: AppHandle, qrcode_key: String) -> CommandRes
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn get_user_info(app: AppHandle, sessdata: String) -> CommandResult<UserInfo> {
let bili_client = app.get_bili_client();
let user_info = bili_client
@@ -128,6 +135,7 @@ pub async fn get_user_info(app: AppHandle, sessdata: String) -> CommandResult<Us
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all, fields(ep_id = params.get_ep_id(), season_id = params.get_season_id()))]
pub async fn get_bangumi_info(
app: AppHandle,
params: GetBangumiInfoParams,
@@ -142,6 +150,7 @@ pub async fn get_bangumi_info(
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all, fields(bvid = params.get_bvid(), aid = params.get_aid()))]
pub async fn get_normal_info(
app: AppHandle,
params: GetNormalInfoParams,
@@ -156,6 +165,7 @@ pub async fn get_normal_info(
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn get_user_video_info(
app: AppHandle,
params: GetUserVideoInfoParams,
@@ -170,6 +180,7 @@ pub async fn get_user_video_info(
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn get_fav_folders(app: AppHandle, uid: i64) -> CommandResult<FavFolders> {
let bili_client = app.get_bili_client();
let fav_folders = bili_client
@@ -181,6 +192,7 @@ pub async fn get_fav_folders(app: AppHandle, uid: i64) -> CommandResult<FavFolde
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn get_fav_info(app: AppHandle, params: GetFavInfoParams) -> CommandResult<FavInfo> {
let bili_client = app.get_bili_client();
let fav_info = bili_client
@@ -192,6 +204,7 @@ pub async fn get_fav_info(app: AppHandle, params: GetFavInfoParams) -> CommandRe
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn get_watch_later_info(app: AppHandle, page: i32) -> CommandResult<WatchLaterInfo> {
let bili_client = app.get_bili_client();
let watch_later_info = bili_client
@@ -203,6 +216,7 @@ pub async fn get_watch_later_info(app: AppHandle, page: i32) -> CommandResult<Wa
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn get_bangumi_follow_info(
app: AppHandle,
params: GetBangumiFollowInfoParams,
@@ -217,6 +231,7 @@ pub async fn get_bangumi_follow_info(
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn get_history_info(
app: AppHandle,
params: GetHistoryInfoParams,
@@ -232,15 +247,16 @@ pub async fn get_history_info(
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub fn create_download_tasks(app: AppHandle, params: CreateDownloadTaskParams) {
let download_manager = app.get_download_manager();
download_manager.create_download_tasks(&params);
tracing::debug!("下载任务创建成功");
}
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub fn pause_download_tasks(app: AppHandle, task_ids: Vec<String>) {
let download_manager = app.get_download_manager();
download_manager.pause_download_tasks(&task_ids);
@@ -249,6 +265,7 @@ pub fn pause_download_tasks(app: AppHandle, task_ids: Vec<String>) {
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub fn resume_download_tasks(app: AppHandle, task_ids: Vec<String>) {
let download_manager = app.get_download_manager();
download_manager.resume_download_tasks(&task_ids);
@@ -257,6 +274,7 @@ pub fn resume_download_tasks(app: AppHandle, task_ids: Vec<String>) {
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub fn delete_download_tasks(app: AppHandle, task_ids: Vec<String>) {
let download_manager = app.get_download_manager();
download_manager.delete_download_tasks(&task_ids);
@@ -265,6 +283,7 @@ pub fn delete_download_tasks(app: AppHandle, task_ids: Vec<String>) {
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub fn restart_download_tasks(app: AppHandle, task_ids: Vec<String>) {
let download_manager = app.get_download_manager();
download_manager.restart_download_tasks(&task_ids);
@@ -273,6 +292,7 @@ pub fn restart_download_tasks(app: AppHandle, task_ids: Vec<String>) {
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all, fields(task_id = params.task_id))]
pub fn restart_download_task(app: AppHandle, params: RestartDownloadTaskParams) {
let download_manager = app.get_download_manager();
download_manager.restart_download_task(&params);
@@ -281,6 +301,7 @@ pub fn restart_download_task(app: AppHandle, params: RestartDownloadTaskParams)
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub fn restore_download_tasks(app: AppHandle) -> CommandResult<()> {
let download_manager = app.get_download_manager();
download_manager
@@ -292,6 +313,7 @@ pub fn restore_download_tasks(app: AppHandle) -> CommandResult<()> {
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn search(app: AppHandle, params: SearchParams) -> CommandResult<SearchResult> {
use SearchParams::{Bangumi, Cheese, Fav, Normal, UserVideo};
let bili_client = app.get_bili_client();
@@ -364,6 +386,7 @@ pub async fn search(app: AppHandle, params: SearchParams) -> CommandResult<Searc
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub fn get_logs_dir_size(app: AppHandle) -> CommandResult<u64> {
let logs_dir = logger::logs_dir(&app)
.wrap_err("获取日志目录失败")
@@ -382,6 +405,7 @@ pub fn get_logs_dir_size(app: AppHandle) -> CommandResult<u64> {
#[allow(clippy::needless_pass_by_value)]
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub fn show_path_in_file_manager(app: AppHandle, path: &str) -> CommandResult<()> {
app.opener()
.reveal_item_in_dir(path)
@@ -392,6 +416,7 @@ pub fn show_path_in_file_manager(app: AppHandle, path: &str) -> CommandResult<()
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all, fields(bvid = bvid, cid = cid))]
pub async fn get_skip_segments(
app: AppHandle,
bvid: String,
@@ -407,6 +432,7 @@ pub async fn get_skip_segments(
#[tauri::command(async)]
#[specta::specta]
#[instrument(level = "error", skip_all)]
pub async fn get_available_media_formats(
app: AppHandle,
params: GetAvailableMediaFormatsParams,
+3
View File
@@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use specta::Type;
use tauri::{AppHandle, Manager};
use tracing::instrument;
use crate::{
danmaku_xml_to_ass::canvas::CanvasConfig,
@@ -47,6 +48,7 @@ pub struct Config {
}
impl Config {
#[instrument(level = "error", skip_all)]
pub fn new(app: &AppHandle) -> eyre::Result<Config> {
let app_data_dir = app.path().app_data_dir()?;
let config_path = app_data_dir.join("config.json");
@@ -67,6 +69,7 @@ impl Config {
Ok(config)
}
#[instrument(level = "error", skip_all)]
pub fn save(&self, app: &AppHandle) -> eyre::Result<()> {
let app_data_dir = app.path().app_data_dir()?;
let config_path = app_data_dir.join("config.json");
@@ -1,8 +1,9 @@
use eyre::Result;
use std::borrow::Cow;
use std::fmt;
use std::io::{BufWriter, Write};
use tracing::instrument;
use super::canvas::CanvasConfig;
use super::drawable::{DrawEffect, Drawable};
@@ -99,7 +100,8 @@ pub struct AssWriter<W: Write> {
}
impl<W: Write> AssWriter<W> {
pub fn new(f: W, title: String, canvas_config: CanvasConfig) -> Result<Self> {
#[instrument(level = "error", skip_all)]
pub fn new(f: W, title: String, canvas_config: CanvasConfig) -> eyre::Result<Self> {
let mut this = AssWriter {
// 对于 HDD、docker 之类的场景,磁盘 IO 是非常大的瓶颈。使用大缓存
f: BufWriter::with_capacity(10 << 20, f),
@@ -112,7 +114,8 @@ impl<W: Write> AssWriter<W> {
Ok(this)
}
pub fn init(&mut self) -> Result<()> {
#[instrument(level = "error", skip_all)]
pub fn init(&mut self) -> eyre::Result<()> {
write!(
self.f,
"\
@@ -147,7 +150,8 @@ impl<W: Write> AssWriter<W> {
Ok(())
}
pub fn write(&mut self, drawable: Drawable) -> Result<()> {
#[instrument(level = "error", skip_all)]
pub fn write(&mut self, drawable: Drawable) -> eyre::Result<()> {
writeln!(
self.f,
// Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
+4
View File
@@ -9,6 +9,7 @@ use ass_writer::AssWriter;
use canvas::CanvasConfig;
use danmaku::{Danmaku, DanmakuType};
use eyre::eyre;
use tracing::instrument;
use yaserde::{YaDeserialize, YaSerialize};
#[derive(YaSerialize, YaDeserialize)]
@@ -28,6 +29,7 @@ pub struct DanmakuXmlITag {
pub elems: Vec<DamakuXmlDTag>,
}
#[instrument(level = "error", skip_all)]
pub fn xml_to_ass(
xml: &str,
ass_file: File,
@@ -58,6 +60,7 @@ trait ToDanmakuType {
}
impl ToDanmakuType for u32 {
#[instrument(level = "error", skip_all)]
fn to_danmaku_type(&self) -> eyre::Result<DanmakuType> {
match self {
1 => Ok(DanmakuType::Float),
@@ -69,6 +72,7 @@ impl ToDanmakuType for u32 {
}
}
#[instrument(level = "error", skip_all)]
pub fn xml_to_danmakus(xml: &str) -> eyre::Result<Vec<Danmaku>> {
let xml = sanitize_xml(xml);
let i_tag: DanmakuXmlITag = yaserde::de::from_str(&xml).map_err(|e| eyre!(e))?;
@@ -7,6 +7,7 @@ use std::{
use parking_lot::Mutex;
use tokio::{sync::SemaphorePermit, time::sleep};
use tracing::instrument;
use crate::{
downloader::{download_task::DownloadTask, download_task_state::DownloadTaskState},
@@ -23,6 +24,16 @@ pub struct DownloadChunkTask {
}
impl DownloadChunkTask {
#[instrument(
level = "error",
skip_all,
fields(
url = self.url,
chunk_index = ?self.chunk_index,
start = self.start,
end = self.end,
)
)]
pub async fn process(self) -> eyre::Result<usize> {
let download_chunk_task = self.download_chunk();
tokio::pin!(download_chunk_task);
@@ -64,6 +75,7 @@ impl DownloadChunkTask {
}
}
#[instrument(level = "error", skip_all)]
async fn download_chunk(&self) -> eyre::Result<usize> {
let bili_client = self.download_task.app.get_bili_client();
let chunk_data = bili_client
@@ -94,6 +106,7 @@ impl DownloadChunkTask {
Ok(self.chunk_index)
}
#[instrument(level = "error", skip_all)]
async fn acquire_chunk_permit<'a>(
&'a self,
permit: &mut Option<SemaphorePermit<'a>>,
+42 -15
View File
@@ -8,11 +8,12 @@ use std::{
time::Duration,
};
use eyre::WrapErr;
use eyre::{WrapErr, eyre};
use parking_lot::RwLock;
use tauri::{AppHandle, Manager};
use tauri_specta::Event;
use tokio::sync::Semaphore;
use tracing::instrument;
use crate::{
events::DownloadEvent,
@@ -59,6 +60,7 @@ impl DownloadManager {
manager
}
#[instrument(level = "error", skip_all)]
pub fn restore_download_tasks(&self) -> eyre::Result<()> {
let task_dir = self.get_task_dir()?;
std::fs::create_dir_all(&task_dir)
@@ -104,98 +106,121 @@ impl DownloadManager {
}
}
#[instrument(level = "error", skip_all)]
pub fn pause_download_tasks(&self, task_ids: &Vec<String>) {
let tasks = self.download_tasks.read();
for task_id in task_ids {
let span = tracing::error_span!("pause_download_task", task_id = task_id);
let _enter = span.enter();
let Some(task) = tasks.get(task_id) else {
let err = eyre!("未找到ID对应的下载任务");
let err_title = "暂停下载任务失败";
let message = format!("未找到ID为`{task_id}`的下载任务");
let message = err.to_message();
tracing::error!(err_title, message);
continue;
};
task.set_state(DownloadTaskState::Paused);
tracing::debug!("已将ID为`{task_id}`的下载任务状态设置为`Paused`");
tracing::debug!("已将ID对应的下载任务状态设置为`Paused`");
}
}
#[instrument(level = "error", skip_all)]
pub fn resume_download_tasks(&self, task_ids: &Vec<String>) {
let tasks = self.download_tasks.read();
for task_id in task_ids {
let span = tracing::error_span!("resume_download_task", task_id = task_id);
let _enter = span.enter();
let Some(task) = tasks.get(task_id) else {
let err = eyre!("未找到ID对应的下载任务");
let err_title = "继续下载任务失败";
let message = format!("未找到ID为`{task_id}`的下载任务");
let message = err.to_message();
tracing::error!(err_title, message);
continue;
};
task.set_state(DownloadTaskState::Pending);
tracing::debug!("已将ID为`{task_id}`的下载任务状态设置为`Pending`");
tracing::debug!("已将ID对应的下载任务状态设置为`Pending`");
}
}
#[instrument(level = "error", skip_all)]
pub fn delete_download_tasks(&self, task_ids: &Vec<String>) {
let mut tasks = self.download_tasks.write();
for task_id in task_ids {
let span = tracing::error_span!("delete_download_task", task_id = task_id);
let _enter = span.enter();
let Some(task) = tasks.remove(task_id) else {
let err = eyre!("未找到ID对应的下载任务");
let err_title = "删除下载任务失败";
let message = format!("未找到ID为`{task_id}`的下载任务");
let message = err.to_message();
tracing::error!(err_title, message);
continue;
};
if let Err(err) = self.delete_progress_file(task_id) {
let err_title = "删除下载任务失败";
let message = format!("删除ID为`{task_id}`的下载任务文件失败: {err}");
let message = err.to_message();
tracing::error!(err_title, message);
tasks.insert(task_id.clone(), task);
continue;
}
if let Err(err) = task.delete_sender.send(()).map_err(eyre::Report::from) {
let err = err.wrap_err("通知ID对应的下载任务删除失败");
let err_title = "删除下载任务失败";
let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务删除失败"));
let message = err.to_message();
tracing::error!(err_title, message);
tasks.insert(task_id.clone(), task);
continue;
}
tracing::debug!("已通知ID为`{task_id}`的下载任务删除");
tracing::debug!("已通知ID对应的下载任务删除");
}
}
#[instrument(level = "error", skip_all)]
pub fn restart_download_tasks(&self, task_ids: &Vec<String>) {
let tasks = self.download_tasks.read();
for task_id in task_ids {
let span = tracing::error_span!("restart_download_task", task_id = task_id);
let _enter = span.enter();
let Some(task) = tasks.get(task_id) else {
let err = eyre!("未找到ID对应的下载任务");
let err_title = "重来下载任务失败";
let message = format!("未找到ID为`{task_id}`的下载任务");
let message = err.to_message();
tracing::error!(err_title, message);
continue;
};
if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) {
let err_title = "重来下载任务失败";
let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务重来失败"));
let err = err.wrap_err("通知ID对应的下载任务重来失败");
let message = err.to_message();
tracing::error!(err_title, message);
continue;
}
tracing::debug!("已通知ID为`{task_id}`的下载任务重来");
tracing::debug!("已通知ID对应的下载任务重来");
}
}
#[instrument(level = "error", skip_all, fields(task_id = params.task_id))]
pub fn restart_download_task(&self, params: &RestartDownloadTaskParams) {
let task_id = &params.task_id;
let tasks = self.download_tasks.read();
let Some(task) = tasks.get(task_id) else {
let err = eyre!("未找到ID对应的下载任务");
let err_title = "重来下载任务失败";
let message = format!("未找到ID为`{task_id}`的下载任务");
let message = err.to_message();
tracing::error!(err_title, message);
return;
};
// TODO: 把这块代码变成DownloadProgress的mark_restart函数
{
let mut progress = task.progress.write();
@@ -219,13 +244,13 @@ impl DownloadManager {
if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) {
let err_title = "重来下载任务失败";
let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务重来失败"));
let err = err.wrap_err("通知ID对应的下载任务重来失败");
let message = err.to_message();
tracing::error!(err_title, message);
return;
}
tracing::debug!("已通知ID为`{task_id}`的下载任务重来");
tracing::debug!("已通知ID对应的下载任务重来");
}
async fn emit_download_speed_loop(app: AppHandle, byte_per_sec: Arc<AtomicU64>) {
@@ -241,12 +266,14 @@ impl DownloadManager {
}
}
#[instrument(level = "error", skip_all)]
fn get_task_dir(&self) -> eyre::Result<PathBuf> {
let app_data_dir = self.app.path().app_data_dir()?;
let task_dir = app_data_dir.join(".下载任务");
Ok(task_dir)
}
#[instrument(level = "error", skip_all, fields(task_id = task_id))]
fn delete_progress_file(&self, task_id: &str) -> eyre::Result<()> {
let task_dir = self.get_task_dir()?;
let task_file = task_dir.join(format!("{task_id}.json"));
+38 -45
View File
@@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
use specta::Type;
use tauri::{AppHandle, Manager};
use tauri_specta::Event;
use tracing::instrument;
use uuid::Uuid;
use crate::{
@@ -71,6 +72,7 @@ pub struct DownloadProgress {
}
impl DownloadProgress {
#[instrument(level = "error", skip_all)]
pub fn from_normal(
app: &AppHandle,
info: &NormalInfo,
@@ -87,10 +89,11 @@ impl DownloadProgress {
}
#[allow(clippy::cast_possible_wrap)]
#[instrument(level = "error", skip_all)]
pub fn from_bangumi(app: &AppHandle, info: &BangumiInfo, ep_id: i64) -> eyre::Result<Self> {
let (episode, episode_order) = info.get_episode_with_order(ep_id)?;
let Some(duration) = episode.duration else {
return Err(eyre!("找不到ep_id为`{ep_id}`的番剧的时长"));
return Err(eyre!("duration为None"));
};
// 将毫秒转换为秒
let duration = duration / 1000;
@@ -98,7 +101,6 @@ impl DownloadProgress {
let config = app.get_config().read().clone();
let tasks = Tasks::new(&config, &episode.cover);
let (up_name, up_uid, up_avatar) = if let Some(up_info) = &info.up_info {
(
Some(up_info.uname.clone()),
@@ -147,12 +149,13 @@ impl DownloadProgress {
Ok(progress)
}
#[instrument(level = "error", skip_all)]
pub fn from_cheese(app: &AppHandle, info: &CheeseInfo, ep_id: i64) -> eyre::Result<Self> {
let episode = info
.episodes
.iter()
.find(|ep| ep.id == ep_id)
.ok_or_eyre(format!("找不到ep_id为`{ep_id}`的课程"))?;
.ok_or_eyre("找不到ep_id对应的课程")?;
let config = app.get_config().read().clone();
@@ -196,9 +199,8 @@ impl DownloadProgress {
Ok(progress)
}
#[instrument(level = "error", skip_all)]
pub async fn process(&mut self, download_task: &Arc<DownloadTask>) -> eyre::Result<()> {
let ids_string = self.get_ids_string();
let _ = DownloadEvent::ProgressPreparing {
task_id: self.task_id.clone(),
}
@@ -211,12 +213,8 @@ impl DownloadProgress {
self.completed_ts = None; // 重置完成时间戳
download_task.update_progress(|p| *p = self.clone());
let (episode_dir, filename) = (&self.episode_dir, &self.filename);
std::fs::create_dir_all(episode_dir).wrap_err(format!(
"{ids_string} 创建目录`{}`失败",
episode_dir.display()
))?;
std::fs::create_dir_all(&self.episode_dir)
.wrap_err(format!("创建目录`{}`失败", self.episode_dir.display()))?;
let video_task = &self.video_task;
let audio_task = &self.audio_task;
@@ -234,16 +232,16 @@ impl DownloadProgress {
video_task
.process(download_task, self)
.await
.wrap_err(format!("{ids_string} `{filename}`下载视频文件失败"))?;
tracing::debug!("{ids_string} `{filename}`视频下载任务完成");
.wrap_err("下载视频文件失败")?;
tracing::debug!("视频下载任务完成");
}
if !audio_task.is_completed() && audio_task.content_length != 0 {
audio_task
.process(download_task, self)
.await
.wrap_err(format!("{ids_string} `{filename}`下载音频文件失败"))?;
tracing::debug!("{ids_string} `{filename}`音频下载任务完成");
.wrap_err("下载音频文件失败")?;
tracing::debug!("音频下载任务完成");
}
let video_process_task_is_completed = video_process_task.is_completed();
@@ -252,55 +250,53 @@ impl DownloadProgress {
p.video_process_task.skipped = true;
p.video_process_task.completed = true;
});
tracing::debug!(
"{ids_string} `{filename}`受版权保护(DRM),无法处理,已跳过视频处理任务"
);
tracing::debug!("受版权保护(DRM),无法处理,已跳过视频处理任务");
} else if !video_process_task_is_completed {
video_process_task
.process(download_task, self, &mut player_info)
.await
.wrap_err(format!("{ids_string} `{filename}`视频处理失败"))?;
tracing::debug!("{ids_string} `{filename}`视频处理任务完成");
.wrap_err("视频处理失败")?;
tracing::debug!("视频处理任务完成");
}
if !danmaku_task.is_completed() {
danmaku_task
.process(download_task, self)
.await
.wrap_err(format!("{ids_string} `{filename}`下载弹幕失败"))?;
tracing::debug!("{ids_string} `{filename}`弹幕下载任务完成");
.wrap_err("下载弹幕失败")?;
tracing::debug!("弹幕下载任务完成");
}
if !subtitle_task.is_completed() {
subtitle_task
.process(download_task, self, &mut player_info)
.await
.wrap_err(format!("{ids_string} `{filename}`下载字幕失败"))?;
tracing::debug!("{ids_string} `{filename}`字幕下载任务完成");
.wrap_err("下载字幕失败")?;
tracing::debug!("字幕下载任务完成");
}
if !cover_task.is_completed() {
cover_task
.process(download_task, self)
.await
.wrap_err(format!("{ids_string} `{filename}`下载封面失败"))?;
tracing::debug!("{ids_string} `{filename}`封面下载任务完成");
.wrap_err("下载封面失败")?;
tracing::debug!("封面下载任务完成");
}
if !nfo_task.is_completed() {
nfo_task
.process(download_task, self, &mut episode_info)
.await
.wrap_err(format!("{ids_string} `{filename}`下载NFO失败"))?;
tracing::debug!("{ids_string} `{filename}`NFO下载任务完成");
.wrap_err("下载NFO失败")?;
tracing::debug!("NFO下载任务完成");
}
if !json_task.is_completed() {
json_task
.process(download_task, self, &mut episode_info)
.await
.wrap_err(format!("{ids_string} `{filename}`下载JSON元数据失败"))?;
tracing::debug!("{ids_string} `{filename}`JSON元数据下载任务完成");
.wrap_err("下载JSON元数据失败")?;
tracing::debug!("JSON元数据下载任务完成");
}
let completed_ts = SystemTime::now()
@@ -314,6 +310,7 @@ impl DownloadProgress {
Ok(())
}
#[instrument(level = "error", skip_all)]
async fn prepare(&mut self, app: &AppHandle) -> eyre::Result<()> {
let video_selected = self.video_task.selected;
let video_completed = self.video_task.completed;
@@ -348,7 +345,7 @@ impl DownloadProgress {
if audio_selected && !audio_completed {
// 如果音频被选中且未完成,则准备音频任务
self.audio_task.prepare_normal(app, &media_url).await?;
self.audio_task.prepare_normal(app, &media_url).await;
}
}
EpisodeType::Bangumi => {
@@ -366,7 +363,7 @@ impl DownloadProgress {
if audio_selected && !audio_completed {
// 如果音频被选中且未完成,则准备音频任务
self.audio_task.prepare_bangumi(app, &media_url).await?;
self.audio_task.prepare_bangumi(app, &media_url).await;
}
}
EpisodeType::Cheese => {
@@ -388,7 +385,7 @@ impl DownloadProgress {
if audio_selected && !audio_completed {
// 如果音频被选中且未完成,则准备音频任务
self.audio_task.prepare_cheese(app, &media_url).await?;
self.audio_task.prepare_cheese(app, &media_url).await;
}
}
}
@@ -399,6 +396,7 @@ impl DownloadProgress {
Ok(())
}
#[instrument(level = "error", skip_all)]
fn update_fmt_fields(&mut self, app: &AppHandle) -> eyre::Result<()> {
let fmt_params = self.create_fmt_params();
@@ -435,6 +433,7 @@ impl DownloadProgress {
}
}
#[instrument(level = "error", skip_all)]
pub fn save(&self, app: &AppHandle, allow_create: bool) -> eyre::Result<()> {
let progress = self.clone();
let file_name = format!("{}.json", progress.task_id);
@@ -475,17 +474,10 @@ impl DownloadProgress {
self.nfo_task.mark_uncompleted();
self.json_task.mark_uncompleted();
}
pub fn get_ids_string(&self) -> String {
let aid = self.aid;
let bvid = self.bvid.as_deref().unwrap_or("None");
let cid = self.cid;
let ep_id = self.ep_id.map_or("None".to_string(), |id| id.to_string());
format!("aid: {aid}, bvid: {bvid}, cid: {cid}, ep_id: {ep_id}")
}
}
#[allow(clippy::too_many_lines)]
#[instrument(level = "error", skip_all)]
fn create_normal_progresses_for_single(
info: &NormalInfo,
cid: Option<i64>,
@@ -498,7 +490,7 @@ fn create_normal_progresses_for_single(
if let Some(cid) = cid {
// 如果有cid,则说明是要下载单个分P
let Some(page) = info.pages.iter().find(|p| p.cid == cid) else {
return Err(eyre!("找不到cid为`{cid}`的分P"));
return Err(eyre!("找不到cid对应的分P"));
};
let progress = DownloadProgress {
task_id: Uuid::new_v4().to_string(),
@@ -615,6 +607,7 @@ fn create_normal_progresses_for_single(
}
#[allow(clippy::too_many_lines)]
#[instrument(level = "error", skip_all)]
fn create_normal_progresses_for_season(
ugc_season: &UgcSeason,
info: &NormalInfo,
@@ -626,7 +619,7 @@ fn create_normal_progresses_for_season(
.sections
.iter()
.position(|s| s.episodes.iter().any(|e| e.aid == aid))
.ok_or_eyre(format!("找不到含有aid为`{aid}`的ep的section"))?;
.ok_or_eyre("找不到含有对应aid的section")?;
let section = &ugc_season.sections[section_index];
#[allow(clippy::cast_possible_wrap)]
let (ep, episode_order) = section
@@ -635,7 +628,7 @@ fn create_normal_progresses_for_season(
.enumerate()
.map(|(i, e)| (e, i as i64 + 1))
.find(|(e, _)| e.aid == aid)
.ok_or_eyre(format!("在section中找不到aid为`{aid}`的ep"))?;
.ok_or_eyre("在section中找不到aid对应的ep")?;
let tasks = Tasks::new(config, &ep.arc.pic);
@@ -644,7 +637,7 @@ fn create_normal_progresses_for_season(
if let Some(cid) = cid {
// 如果有cid,则说明是要下载单个分P
let Some(page) = ep.pages.iter().find(|p| p.cid == cid) else {
return Err(eyre!("找不到cid为`{cid}`的分P"));
return Err(eyre!("找不到cid对应的分P"));
};
let progress = DownloadProgress {
task_id: Uuid::new_v4().to_string(),
+149 -39
View File
@@ -1,4 +1,4 @@
use std::{sync::Arc, time::Duration};
use std::{sync::Arc, time::Duration};
use eyre::WrapErr;
use parking_lot::RwLock;
@@ -8,8 +8,10 @@ use tokio::{
sync::{SemaphorePermit, watch},
time::sleep,
};
use tracing::instrument;
use crate::{
downloader::episode_type::EpisodeType,
events::DownloadEvent,
extensions::{AppHandleExt, EyreReportToMessage},
types::create_download_task_params::CreateDownloadTaskParams,
@@ -24,10 +26,13 @@ pub struct DownloadTask {
pub cancel_sender: watch::Sender<()>,
pub delete_sender: watch::Sender<()>,
pub task_id: String,
pub trace_fields: DownloadTaskTraceFields,
pub progress: RwLock<DownloadProgress>,
}
impl DownloadTask {
#[allow(clippy::too_many_lines)]
#[instrument(level = "error", skip_all)]
pub fn from_params(app: &AppHandle, params: &CreateDownloadTaskParams) -> Vec<Arc<Self>> {
use CreateDownloadTaskParams::{Bangumi, Cheese, Normal};
@@ -35,13 +40,22 @@ impl DownloadTask {
match params {
Normal(params) => {
for &(aid, cid) in &params.aid_cid_pairs {
let span = tracing::error_span!(
"from_params_normal",
aid = aid,
bvid = params.info.bvid,
cid = cid,
collection_title = params.info.title,
up_name = params.info.owner.name,
up_uid = params.info.owner.mid,
);
let _enter = span.enter();
let progress = match DownloadProgress::from_normal(app, &params.info, aid, cid)
{
Ok(progress) => progress,
Err(err) => {
let cid = cid.map_or("None".to_string(), |id| id.to_string());
let ids_string = format!("aid: {aid}, cid: {cid}");
let err_title = format!("{ids_string} 创建普通视频的下载进度失败");
let err_title = "创建普通视频的下载进度失败";
let message = err.to_message();
tracing::error!(err_title, message);
continue;
@@ -53,11 +67,19 @@ impl DownloadTask {
}
Bangumi(params) => {
for ep_id in &params.ep_ids {
let span = tracing::error_span!(
"from_params_bangumi",
ep_id = ep_id,
collection_title = params.info.title,
up_name = params.info.up_info.as_ref().map(|up_info| &up_info.uname),
up_uid = params.info.up_info.as_ref().map(|up_info| up_info.mid),
);
let _enter = span.enter();
let progress = match DownloadProgress::from_bangumi(app, &params.info, *ep_id) {
Ok(progress) => progress,
Err(err) => {
let ids_string = format!("ep_id: {ep_id}");
let err_title = format!("{ids_string} 创建番剧的下载进度失败");
let err_title = "创建番剧的下载进度失败";
let message = err.to_message();
tracing::error!(err_title, message);
continue;
@@ -69,11 +91,19 @@ impl DownloadTask {
}
Cheese(params) => {
for ep_id in &params.ep_ids {
let span = tracing::error_span!(
"from_params_cheese",
ep_id = ep_id,
collection_title = params.info.title,
up_name = params.info.up_info.uname,
up_uid = params.info.up_info.mid,
);
let _enter = span.enter();
let progress = match DownloadProgress::from_cheese(app, &params.info, *ep_id) {
Ok(progress) => progress,
Err(err) => {
let ids_string = format!("ep_id: {ep_id}");
let err_title = format!("{ids_string} 创建课程的下载进度失败");
let err_title = "创建课程的下载进度失败";
let message = err.to_message();
tracing::error!(err_title, message);
continue;
@@ -87,10 +117,26 @@ impl DownloadTask {
let mut tasks = Vec::new();
for progress in progresses {
let span = tracing::error_span!(
"create_tasks",
task_id = progress.task_id,
episode_type = ?progress.episode_type,
aid = progress.aid,
bvid = progress.bvid,
cid = progress.cid,
ep_id = progress.ep_id,
collection_title = progress.collection_title,
episode_title = progress.episode_title,
episode_order = progress.episode_order,
part_title = progress.part_title,
part_order = progress.part_order,
up_name = progress.up_name,
up_uid = progress.up_uid,
);
let _enter = span.enter();
if let Err(err) = progress.save(app, true) {
let ids_string = progress.get_ids_string();
let episode_title = &progress.episode_title;
let err_title = format!("{ids_string} `{episode_title}`保存下载进度到文件失败");
let err_title = "保存下载进度到文件失败";
let message = err.to_message();
tracing::error!(err_title, message);
}
@@ -114,6 +160,7 @@ impl DownloadTask {
cancel_sender,
delete_sender,
task_id: progress.task_id.clone(),
trace_fields: DownloadTaskTraceFields::from(&progress),
progress: RwLock::new(progress),
});
@@ -143,6 +190,7 @@ impl DownloadTask {
cancel_sender,
delete_sender,
task_id: progress.task_id.clone(),
trace_fields: DownloadTaskTraceFields::from(&progress),
progress: RwLock::new(progress),
});
@@ -151,8 +199,26 @@ impl DownloadTask {
task
}
#[instrument(
level = "error",
skip_all,
fields(
task_id = self.trace_fields.task_id,
episode_type = ?self.trace_fields.episode_type,
aid = self.trace_fields.aid,
bvid = self.trace_fields.bvid,
cid = self.trace_fields.cid,
ep_id = self.trace_fields.ep_id,
collection_title = self.trace_fields.collection_title,
episode_title = self.trace_fields.episode_title,
episode_order = self.trace_fields.episode_order,
part_title = self.trace_fields.part_title,
part_order = self.trace_fields.part_order,
up_name = self.trace_fields.up_name,
up_uid = self.trace_fields.up_uid,
)
)]
async fn process(self: Arc<Self>) {
let task_id = &self.task_id;
let state = *self.state_sender.borrow();
let progress = self.progress.read().clone();
let _ = DownloadEvent::TaskCreate { state, progress }.emit(&self.app);
@@ -194,7 +260,7 @@ impl DownloadTask {
_ = restart_receiver.changed() => {
self.handle_restart_notify();
tracing::debug!("ID为`{task_id}`的下载任务已重来");
tracing::debug!("下载任务已重来");
download_task_option = None;
}
@@ -212,31 +278,30 @@ impl DownloadTask {
sleep(Duration::from_millis(100)).await;
}
tracing::debug!("ID为`{task_id}`的下载任务已删除");
tracing::debug!("下载任务已删除");
return;
}
}
}
}
#[instrument(level = "error", skip_all)]
async fn download(self: &Arc<Self>) {
let mut progress = self.progress.read().clone();
let ids_string = progress.get_ids_string();
let episode_title = progress.episode_title.clone();
if progress.is_completed() {
tracing::info!("{ids_string} 跳过`{episode_title}`的下载,因为它已经完成");
tracing::info!("跳过下载,因为下载任务已完成");
self.set_state(DownloadTaskState::Completed);
return;
}
tracing::debug!("{ids_string} 开始下载`{episode_title}`");
tracing::debug!("开始下载");
if let Err(err) = progress
.process(self)
.await
.wrap_err("[继续]失败的任务可以断点续传")
{
let err_title = format!("{ids_string} `{episode_title}`下载失败");
let err_title = "下载失败";
let message = err.to_message();
tracing::error!(err_title, message);
@@ -248,7 +313,7 @@ impl DownloadTask {
self.sleep_between_task().await;
self.set_state(DownloadTaskState::Completed);
tracing::info!("{ids_string} `{episode_title}`下载完成");
tracing::info!("下载成功");
}
async fn sleep_between_task(&self) {
@@ -266,12 +331,8 @@ impl DownloadTask {
}
}
#[instrument(level = "error", skip_all)]
async fn acquire_task_permit<'a>(&'a self, permit: &mut Option<SemaphorePermit<'a>>) {
let (episode_title, ids_string) = {
let progress = self.progress.read();
(progress.episode_title.clone(), progress.get_ids_string())
};
*permit = match permit.take() {
// 如果有permit,则直接用
Some(permit) => Some(permit),
@@ -287,8 +348,7 @@ impl DownloadTask {
{
Ok(permit) => Some(permit),
Err(err) => {
let err_title =
format!("{ids_string} `{episode_title}`获取下载任务的permit失败");
let err_title = "获取下载任务的permit失败";
let message = err.to_message();
tracing::error!(err_title, message);
@@ -308,7 +368,7 @@ impl DownloadTask {
.send(DownloadTaskState::Downloading)
.map_err(eyre::Report::from)
{
let err_title = format!("{ids_string} `{episode_title}`发送状态`Downloading`失败");
let err_title = "发送状态`Downloading`失败";
let message = err.to_message();
tracing::error!(err_title, message);
@@ -316,6 +376,7 @@ impl DownloadTask {
}
}
#[instrument(level = "error", skip_all)]
async fn handle_state_change<'a>(
&'a self,
permit: &mut Option<SemaphorePermit<'a>>,
@@ -329,14 +390,14 @@ impl DownloadTask {
// 稍微等一下再释放permit
// 避免大批量暂停时,本应暂停的任务因拿到permit而稍微下载一小段(虽然最终会被暂停)
sleep(Duration::from_millis(100)).await;
let task_id = &self.task_id;
tracing::debug!("ID为`{task_id}`的下载任务已暂停");
tracing::debug!("下载任务已暂停");
if let Some(permit) = permit.take() {
drop(permit);
}
}
}
#[instrument(level = "error", skip_all)]
fn handle_restart_notify(&self) {
self.update_progress(|p| {
p.mark_uncompleted();
@@ -344,19 +405,34 @@ impl DownloadTask {
self.set_state(DownloadTaskState::Pending);
}
#[instrument(
level = "error",
skip_all,
fields(
task_id = self.trace_fields.task_id,
episode_type = ?self.trace_fields.episode_type,
aid = self.trace_fields.aid,
bvid = self.trace_fields.bvid,
cid = self.trace_fields.cid,
ep_id = self.trace_fields.ep_id,
collection_title = self.trace_fields.collection_title,
episode_title = self.trace_fields.episode_title,
episode_order = self.trace_fields.episode_order,
part_title = self.trace_fields.part_title,
part_order = self.trace_fields.part_order,
up_name = self.trace_fields.up_name,
up_uid = self.trace_fields.up_uid,
)
)]
pub fn set_state(&self, state: DownloadTaskState) {
let (episode_title, ids_string) = {
let progress = self.progress.read();
(progress.episode_title.clone(), progress.get_ids_string())
};
if let Err(err) = self.state_sender.send(state).map_err(eyre::Report::from) {
let err_title = format!("{ids_string} `{episode_title}`发送状态`{state:?}`失败");
let err_title = format!("发送状态`{state:?}`失败");
let message = err.to_message();
tracing::error!(err_title, message);
}
}
#[instrument(level = "error", skip_all)]
pub fn update_progress(&self, update_fn: impl FnOnce(&mut DownloadProgress)) {
// 修改数据
let updated_progress = {
@@ -371,11 +447,45 @@ impl DownloadTask {
.emit(&self.app);
if let Err(err) = updated_progress.save(&self.app, false) {
let ids_string = updated_progress.get_ids_string();
let episode_title = &updated_progress.episode_title;
let err_title = format!("{ids_string} `{episode_title}`保存下载进度到文件失败");
let err_title = "保存下载进度到文件失败";
let message = err.to_message();
tracing::error!(err_title, message);
}
}
}
pub struct DownloadTaskTraceFields {
pub task_id: String,
pub episode_type: EpisodeType,
pub aid: i64,
pub bvid: Option<String>,
pub cid: i64,
pub ep_id: Option<i64>,
pub collection_title: String,
pub episode_title: String,
pub episode_order: i64,
pub part_title: Option<String>,
pub part_order: Option<i64>,
pub up_name: Option<String>,
pub up_uid: Option<i64>,
}
impl From<&DownloadProgress> for DownloadTaskTraceFields {
fn from(progress: &DownloadProgress) -> Self {
Self {
task_id: progress.task_id.clone(),
episode_type: progress.episode_type,
aid: progress.aid,
bvid: progress.bvid.clone(),
cid: progress.cid,
ep_id: progress.ep_id,
collection_title: progress.collection_title.clone(),
episode_title: progress.episode_title.clone(),
episode_order: progress.episode_order,
part_title: progress.part_title.clone(),
part_order: progress.part_order,
up_name: progress.up_name.clone(),
up_uid: progress.up_uid,
}
}
}
+2
View File
@@ -1,5 +1,6 @@
use eyre::{OptionExt, WrapErr};
use tauri::AppHandle;
use tracing::instrument;
use crate::{
downloader::{download_progress::DownloadProgress, episode_type::EpisodeType},
@@ -27,6 +28,7 @@ pub trait GetOrInitEpisodeInfo {
}
impl GetOrInitEpisodeInfo for Option<EpisodeInfo> {
#[instrument(level = "error", skip_all)]
async fn get_or_init<'a>(
&'a mut self,
app: &AppHandle,
+2
View File
@@ -3,6 +3,7 @@ use std::{collections::HashMap, path::PathBuf};
use eyre::{OptionExt, WrapErr};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use tracing::instrument;
use crate::{
config::Config,
@@ -36,6 +37,7 @@ pub struct FmtParams {
}
impl FmtParams {
#[instrument(level = "error", skip_all)]
pub fn get_episode_dir_and_filename(&self, config: &Config) -> eyre::Result<(PathBuf, String)> {
use strfmt::strfmt;
+39 -49
View File
@@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
use specta::Type;
use tauri::AppHandle;
use tokio::task::JoinSet;
use tracing::{Instrument, instrument};
use crate::{
config::FileExistAction,
@@ -41,11 +42,7 @@ pub struct AudioTask {
}
impl AudioTask {
pub async fn prepare_normal(
&mut self,
app: &AppHandle,
media_url: &NormalMediaUrl,
) -> eyre::Result<()> {
pub async fn prepare_normal(&mut self, app: &AppHandle, media_url: &NormalMediaUrl) {
let mut join_set = JoinSet::new();
if let Some(medias) = &media_url.dash.audio {
@@ -57,7 +54,7 @@ impl AudioTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.base_url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length =
bili_client.get_url_with_content_length(urls).await;
@@ -65,7 +62,9 @@ impl AudioTask {
id,
url_with_content_length,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
}
@@ -78,7 +77,7 @@ impl AudioTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.base_url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length =
bili_client.get_url_with_content_length(urls).await;
@@ -86,7 +85,9 @@ impl AudioTask {
id,
url_with_content_length,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
}
@@ -99,14 +100,16 @@ impl AudioTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.base_url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length = bili_client.get_url_with_content_length(urls).await;
MediaForPrepare {
id,
url_with_content_length,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
let mut medias: Vec<MediaForPrepare> = Vec::new();
@@ -122,31 +125,25 @@ impl AudioTask {
}
self.prepare(app, &medias);
Ok(())
}
pub async fn prepare_bangumi(
&mut self,
app: &AppHandle,
media_url: &BangumiMediaUrl,
) -> eyre::Result<()> {
pub async fn prepare_bangumi(&mut self, app: &AppHandle, media_url: &BangumiMediaUrl) {
let Some(dash) = &media_url.dash else {
// 如果没有音频,则直接返回
self.completed = true;
return Ok(());
return;
};
let Some(medias) = &dash.audio else {
// 如果没有音频,则直接返回
self.completed = true;
return Ok(());
return;
};
if medias.is_empty() {
// 如果没有音频,则直接返回
self.completed = true;
return Ok(());
return;
}
let mut join_set = JoinSet::new();
@@ -159,14 +156,16 @@ impl AudioTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.base_url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length = bili_client.get_url_with_content_length(urls).await;
MediaForPrepare {
id,
url_with_content_length,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
let mut medias: Vec<MediaForPrepare> = Vec::new();
@@ -182,31 +181,25 @@ impl AudioTask {
}
self.prepare(app, &medias);
Ok(())
}
pub async fn prepare_cheese(
&mut self,
app: &AppHandle,
media_url: &CheeseMediaUrl,
) -> eyre::Result<()> {
pub async fn prepare_cheese(&mut self, app: &AppHandle, media_url: &CheeseMediaUrl) {
let Some(dash) = &media_url.dash else {
// 如果没有音频,则直接返回
self.completed = true;
return Ok(());
return;
};
let Some(medias) = &dash.audio else {
// 如果没有音频,则直接返回
self.completed = true;
return Ok(());
return;
};
if medias.is_empty() {
// 如果没有音频,则直接返回
self.completed = true;
return Ok(());
return;
}
let mut join_set = JoinSet::new();
@@ -219,14 +212,16 @@ impl AudioTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.base_url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length = bili_client.get_url_with_content_length(urls).await;
MediaForPrepare {
id,
url_with_content_length,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
let mut medias: Vec<MediaForPrepare> = Vec::new();
@@ -242,8 +237,6 @@ impl AudioTask {
}
self.prepare(app, &medias);
Ok(())
}
fn prepare(&mut self, app: &AppHandle, medias: &[MediaForPrepare]) {
@@ -310,24 +303,19 @@ impl AudioTask {
}
#[allow(clippy::too_many_lines)]
#[instrument(level = "error", skip_all)]
pub async fn process(
&self,
download_task: &Arc<DownloadTask>,
progress: &DownloadProgress,
) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let (audio_task, episode_title, ids_string) = {
(
progress.audio_task.clone(),
progress.episode_title.clone(),
progress.get_ids_string(),
)
};
let audio_task = progress.audio_task.clone();
let m4a_path = episode_dir.join(format!("{filename}.m4a"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && m4a_path.exists() {
tracing::debug!("{ids_string} `{filename}`音频文件已存在,跳过下载");
tracing::debug!("音频文件已存在,跳过下载");
download_task.update_progress(|p| {
p.audio_task.skipped = true;
p.audio_task.completed = true;
@@ -377,11 +365,13 @@ impl AudioTask {
chunk_index,
};
join_set.spawn(async move {
let chunk_order = chunk_index + 1;
let chunk_task = async move {
download_chunk_task.process().await.wrap_err(format!(
"分片`{chunk_index}/{chunk_count}`下载失败({start}-{end})"
"分片`{chunk_order}/{chunk_count}`下载失败({start}-{end})"
))
});
};
join_set.spawn(chunk_task.in_current_span());
}
while let Some(join_result) = join_set.join_next().await {
@@ -392,7 +382,7 @@ impl AudioTask {
match download_audio_result {
Ok(i) => download_task.update_progress(|p| p.audio_task.chunks[i].completed = true),
Err(err) => {
let err_title = format!("{ids_string} `{episode_title}`音频的一个分片下载失败");
let err_title = "音频的一个分片下载失败";
let message = err.to_message();
tracing::error!(err_title, message);
}
@@ -3,6 +3,7 @@ use std::sync::Arc;
use eyre::WrapErr;
use serde::{Deserialize, Serialize};
use specta::Type;
use tracing::instrument;
use crate::{
downloader::{download_progress::DownloadProgress, download_task::DownloadTask},
@@ -26,6 +27,7 @@ impl CoverTask {
!self.selected || self.completed
}
#[instrument(level = "error", skip_all)]
pub async fn process(
&self,
download_task: &Arc<DownloadTask>,
@@ -3,6 +3,7 @@ use std::{fs::File, sync::Arc};
use eyre::WrapErr;
use serde::{Deserialize, Serialize};
use specta::Type;
use tracing::instrument;
use crate::{
config::FileExistAction,
@@ -33,6 +34,7 @@ impl DanmakuTask {
!self.xml_selected && !self.ass_selected && !self.json_selected || self.completed
}
#[instrument(level = "error", skip_all)]
pub async fn process(
&self,
download_task: &Arc<DownloadTask>,
@@ -40,7 +42,6 @@ impl DanmakuTask {
) -> eyre::Result<()> {
let danmaku_task = &progress.danmaku_task;
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string();
let xml_path = episode_dir.join(format!("{filename}.弹幕.xml"));
let ass_path = episode_dir.join(format!("{filename}.弹幕.ass"));
@@ -53,7 +54,7 @@ impl DanmakuTask {
let skip_json = !danmaku_task.json_selected || json_path.exists();
if skip_xml && skip_ass && skip_json {
tracing::debug!("{ids_string} `{filename}`弹幕文件已存在,跳过下载");
tracing::debug!("弹幕文件已存在,跳过下载");
download_task.update_progress(|p| {
p.danmaku_task.skipped = true;
p.danmaku_task.completed = true;
@@ -3,6 +3,7 @@ use std::sync::Arc;
use eyre::WrapErr;
use serde::{Deserialize, Serialize};
use specta::Type;
use tracing::instrument;
use crate::downloader::{
download_progress::DownloadProgress,
@@ -26,6 +27,7 @@ impl JsonTask {
!self.selected || self.completed
}
#[instrument(level = "error", skip_all)]
pub async fn process(
&self,
download_task: &Arc<DownloadTask>,
+14 -7
View File
@@ -4,6 +4,7 @@ use chrono::{DateTime, Datelike, NaiveDateTime};
use eyre::{OptionExt, WrapErr, eyre};
use serde::{Deserialize, Serialize};
use specta::Type;
use tracing::instrument;
use yaserde::{YaDeserialize, YaSerialize};
use crate::{
@@ -37,6 +38,7 @@ impl NfoTask {
!self.selected || self.completed
}
#[instrument(level = "error", skip_all)]
pub async fn process(
&self,
download_task: &Arc<DownloadTask>,
@@ -64,6 +66,7 @@ impl NfoTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
async fn process_normal(
&self,
download_task: &Arc<DownloadTask>,
@@ -71,12 +74,11 @@ impl NfoTask {
info: &NormalInfo,
) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string();
let nfo_path = episode_dir.join(format!("{filename}.nfo"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && nfo_path.exists() {
tracing::debug!("{ids_string} `{filename}`NFO文件已存在,跳过下载");
tracing::debug!("NFO文件已存在,跳过下载");
download_task.update_progress(|p| {
p.nfo_task.skipped = true;
p.nfo_task.completed = true;
@@ -114,6 +116,7 @@ impl NfoTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
async fn process_bangumi(
&self,
download_task: &Arc<DownloadTask>,
@@ -122,12 +125,11 @@ impl NfoTask {
ep_id: &i64,
) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string();
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && episode_details_nfo_path.exists() {
tracing::debug!("{ids_string} `{filename}`NFO文件已存在,跳过下载");
tracing::debug!("NFO文件已存在,跳过下载");
download_task.update_progress(|p| {
p.nfo_task.skipped = true;
p.nfo_task.completed = true;
@@ -178,6 +180,7 @@ impl NfoTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
async fn process_cheese(
&self,
download_task: &Arc<DownloadTask>,
@@ -186,12 +189,11 @@ impl NfoTask {
ep_id: &i64,
) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string();
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && episode_details_nfo_path.exists() {
tracing::debug!("{ids_string} `{filename}`NFO文件已存在,跳过下载");
tracing::debug!("NFO文件已存在,跳过下载");
download_task.update_progress(|p| {
p.nfo_task.skipped = true;
p.nfo_task.completed = true;
@@ -299,6 +301,7 @@ struct EpisodeDetails {
}
impl NormalInfo {
#[instrument(level = "error", skip_all)]
pub fn to_movie_nfo(&self, tags: Tags) -> eyre::Result<String> {
let genre = vec![
"Bilibili视频".to_string(),
@@ -364,6 +367,7 @@ impl NormalInfo {
}
impl BangumiInfo {
#[instrument(level = "error", skip_all)]
pub fn to_tvshow_nfo(&self) -> eyre::Result<String> {
let time_str = &self.publish.pub_time;
let date_time = NaiveDateTime::parse_from_str(time_str, "%Y-%m-%d %H:%M:%S").wrap_err(
@@ -399,6 +403,7 @@ impl BangumiInfo {
Ok(nfo)
}
#[instrument(level = "error", skip_all)]
pub fn to_episode_details_nfo(&self, ep_id: i64) -> eyre::Result<String> {
let (episode, episode_order) = self.get_episode_with_order(ep_id)?;
@@ -489,6 +494,7 @@ impl BangumiInfo {
}
impl CheeseInfo {
#[instrument(level = "error", skip_all)]
pub fn to_tvshow_nfo(&self) -> eyre::Result<String> {
let episode = self.episodes.first().ok_or_eyre("episodes列表为空")?;
let ts = episode.release_date;
@@ -525,12 +531,13 @@ impl CheeseInfo {
Ok(nfo)
}
#[instrument(level = "error", skip_all)]
pub fn to_episode_details_nfo(&self, ep_id: i64) -> eyre::Result<String> {
let episode = self
.episodes
.iter()
.find(|ep| ep.id == ep_id)
.ok_or_eyre(format!("找不到ep_id为`{ep_id}`的课程"))?;
.ok_or_eyre("找不到ep_id对应的课程")?;
let ts = episode.release_date;
let date_time = DateTime::from_timestamp(ts, 0)
@@ -3,6 +3,7 @@ use std::sync::Arc;
use eyre::WrapErr;
use serde::{Deserialize, Serialize};
use specta::Type;
use tracing::instrument;
use crate::{
downloader::{download_progress::DownloadProgress, download_task::DownloadTask},
@@ -27,6 +28,7 @@ impl SubtitleTask {
!self.selected || self.completed
}
#[instrument(level = "error", skip_all)]
pub async fn process(
&self,
download_task: &Arc<DownloadTask>,
@@ -4,6 +4,7 @@ use eyre::{WrapErr, eyre};
use serde::{Deserialize, Serialize};
use specta::Type;
use tauri::AppHandle;
use tracing::instrument;
use crate::{
downloader::{
@@ -38,6 +39,7 @@ impl VideoProcessTask {
|| self.completed
}
#[instrument(level = "error", skip_all)]
pub async fn process(
&self,
download_task: &Arc<DownloadTask>,
@@ -63,6 +65,7 @@ impl VideoProcessTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
async fn merge_and_embed(
&self,
download_task: &Arc<DownloadTask>,
@@ -101,7 +104,10 @@ impl VideoProcessTask {
let metadata_path_clone = metadata_path.clone();
let output_path_clone = output_path.clone();
tokio::spawn(async move {
let current_span = tracing::Span::current();
tauri::async_runtime::spawn_blocking(move || {
let _enter = current_span.enter();
let mut command = std::process::Command::new(ffmpeg_program);
command.arg("-i").arg(video_path_clone);
@@ -162,6 +168,7 @@ impl VideoProcessTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
async fn merge(
&self,
download_task: &Arc<DownloadTask>,
@@ -190,7 +197,10 @@ impl VideoProcessTask {
let audio_path_clone = audio_path.clone();
let output_path_clone = output_path.clone();
let current_span = tracing::Span::current();
tauri::async_runtime::spawn_blocking(move || {
let _enter = current_span.enter();
let mut command = std::process::Command::new(ffmpeg_program);
command.arg("-i").arg(video_path_clone);
@@ -240,6 +250,7 @@ impl VideoProcessTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
async fn embed(
&self,
download_task: &Arc<DownloadTask>,
@@ -273,7 +284,10 @@ impl VideoProcessTask {
let metadata_path_clone = metadata_path.clone();
let output_path_clone = output_path.clone();
let current_span = tracing::Span::current();
tauri::async_runtime::spawn_blocking(move || {
let _enter = current_span.enter();
let mut command = std::process::Command::new(ffmpeg_program);
command.arg("-i").arg(video_path_clone);
@@ -324,6 +338,7 @@ impl VideoProcessTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
async fn create_chapter_metadata(
&self,
app: &AppHandle,
+39 -28
View File
@@ -11,6 +11,7 @@ use serde::{Deserialize, Serialize};
use specta::Type;
use tauri::AppHandle;
use tokio::task::JoinSet;
use tracing::{Instrument, instrument};
use crate::{
config::FileExistAction,
@@ -42,6 +43,7 @@ pub struct VideoTask {
}
impl VideoTask {
#[instrument(level = "error", skip_all)]
pub async fn prepare_normal(
&mut self,
app: &AppHandle,
@@ -58,7 +60,7 @@ impl VideoTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.base_url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length = bili_client.get_url_with_content_length(urls).await;
MediaForPrepare {
@@ -66,7 +68,9 @@ impl VideoTask {
url_with_content_length,
codecid,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
for durl in &media_url.durl {
@@ -78,7 +82,7 @@ impl VideoTask {
urls.extend_from_slice(&durl.backup_url);
urls.push(durl.url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length = bili_client.get_url_with_content_length(urls).await;
MediaForPrepare {
@@ -86,7 +90,9 @@ impl VideoTask {
url_with_content_length,
codecid,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
let mut medias: Vec<MediaForPrepare> = Vec::new();
@@ -106,6 +112,7 @@ impl VideoTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
pub async fn prepare_bangumi(
&mut self,
app: &AppHandle,
@@ -125,7 +132,7 @@ impl VideoTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.base_url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length =
bili_client.get_url_with_content_length(urls).await;
@@ -134,7 +141,9 @@ impl VideoTask {
url_with_content_length,
codecid,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
}
@@ -148,7 +157,7 @@ impl VideoTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length =
bili_client.get_url_with_content_length(urls).await;
@@ -157,7 +166,9 @@ impl VideoTask {
url_with_content_length,
codecid,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
}
@@ -176,6 +187,7 @@ impl VideoTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
pub async fn prepare_cheese(
&mut self,
app: &AppHandle,
@@ -195,7 +207,7 @@ impl VideoTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.base_url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length =
bili_client.get_url_with_content_length(urls).await;
@@ -204,7 +216,9 @@ impl VideoTask {
url_with_content_length,
codecid,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
}
@@ -218,7 +232,7 @@ impl VideoTask {
urls.extend_from_slice(&media.backup_url);
urls.push(media.url.clone());
join_set.spawn(async move {
let get_url_with_content_length_task = async move {
let bili_client = app.get_bili_client();
let url_with_content_length =
bili_client.get_url_with_content_length(urls).await;
@@ -227,7 +241,9 @@ impl VideoTask {
url_with_content_length,
codecid,
}
});
};
join_set.spawn(get_url_with_content_length_task.in_current_span());
}
}
@@ -246,6 +262,7 @@ impl VideoTask {
Ok(())
}
#[instrument(level = "error", skip_all)]
fn prepare(&mut self, app: &AppHandle, medias: &[MediaForPrepare]) -> eyre::Result<()> {
if medias.is_empty() {
return Err(eyre!("获取视频地址失败,medias为空"));
@@ -318,25 +335,19 @@ impl VideoTask {
}
#[allow(clippy::too_many_lines)]
#[instrument(level = "error", skip_all)]
pub async fn process(
&self,
download_task: &Arc<DownloadTask>,
progress: &DownloadProgress,
) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let (video_task, episode_title, ids_string) = {
let progress = download_task.progress.read();
(
progress.video_task.clone(),
progress.episode_title.clone(),
progress.get_ids_string(),
)
};
let video_task = download_task.progress.read().video_task.clone();
let mp4_path = episode_dir.join(format!("{filename}.mp4"));
let file_exist_action = download_task.app.get_config().read().file_exist_action;
if file_exist_action == FileExistAction::Skip && mp4_path.exists() {
tracing::debug!("{ids_string} `{filename}`视频文件已存在,跳过下载");
tracing::debug!("视频文件已存在,跳过下载");
download_task.update_progress(|p| {
p.video_task.skipped = true;
p.video_task.completed = true;
@@ -370,7 +381,7 @@ impl VideoTask {
let chunk_count = video_task.chunks.len();
let mut join_set = JoinSet::new();
for (i, chunk) in video_task.chunks.iter().enumerate() {
for (chunk_index, chunk) in video_task.chunks.iter().enumerate() {
if chunk.completed {
continue;
}
@@ -383,16 +394,16 @@ impl VideoTask {
end,
url: video_task.url.clone(),
file: file.clone(),
chunk_index: i,
chunk_index,
};
let chunk_order = i + 1;
join_set.spawn(async move {
let chunk_order = chunk_index + 1;
let chunk_task = async move {
download_chunk_task.process().await.wrap_err(format!(
"分片`{chunk_order}/{chunk_count}`下载失败({start}-{end})"
))
});
};
join_set.spawn(chunk_task.in_current_span());
}
while let Some(join_result) = join_set.join_next().await {
@@ -403,7 +414,7 @@ impl VideoTask {
match download_video_result {
Ok(i) => download_task.update_progress(|p| p.video_task.chunks[i].completed = true),
Err(err) => {
let err_title = format!("{ids_string} `{episode_title}`视频的一个分片下载失败");
let err_title = "视频的一个分片下载失败";
let message = err.to_message();
tracing::error!(err_title, message);
}
+2
View File
@@ -3,6 +3,7 @@ use std::panic::Location;
use eyre::EyreHandler;
use serde::{Deserialize, Serialize};
use specta::Type;
use tracing::instrument;
use tracing_error::SpanTrace;
pub type CommandResult<T> = Result<T, CommandError>;
@@ -73,6 +74,7 @@ impl EyreHandler for CustomEyreHandler {
}
}
#[instrument(level = "error", skip_all)]
pub fn install_custom_eyre_handler() -> eyre::Result<()> {
eyre::set_hook(Box::new(|_error| {
Box::new(CustomEyreHandler {
+2
View File
@@ -1,6 +1,7 @@
use eyre::WrapErr;
use parking_lot::RwLock;
use tauri::{AppHandle, Manager, State};
use tracing::instrument;
use crate::{
bili_client::BiliClient,
@@ -46,6 +47,7 @@ pub trait GetOrInitPlayerInfo {
}
impl GetOrInitPlayerInfo for Option<PlayerInfo> {
#[instrument(level = "error", skip_all)]
async fn get_or_init<'a>(
&'a mut self,
app: &AppHandle,
+12 -3
View File
@@ -4,7 +4,7 @@ use eyre::{OptionExt, WrapErr};
use notify::{RecommendedWatcher, Watcher};
use tauri::{AppHandle, Manager};
use tauri_specta::Event;
use tracing::{Level, Subscriber};
use tracing::{Instrument, Level, Subscriber, instrument};
use tracing_appender::{
non_blocking::WorkerGuard,
rolling::{RollingFileAppender, Rotation},
@@ -52,6 +52,7 @@ impl Write for LogEventWriter {
static RELOAD_FN: OnceLock<Box<dyn Fn() -> eyre::Result<()> + Send + Sync>> = OnceLock::new();
static GUARD: OnceLock<parking_lot::Mutex<Option<WorkerGuard>>> = OnceLock::new();
#[instrument(level = "error", skip_all)]
pub fn init(app: &AppHandle) -> eyre::Result<()> {
let lib_module_path = module_path!();
let lib_target = lib_module_path.split("::").next().ok_or_eyre(format!(
@@ -104,10 +105,12 @@ pub fn init(app: &AppHandle) -> eyre::Result<()> {
Ok(())
}
#[instrument(level = "error", skip_all)]
pub fn reload_file_logger() -> eyre::Result<()> {
RELOAD_FN.get().ok_or_eyre("RELOAD_FN未初始化")?()
}
#[instrument(level = "error", skip_all)]
pub fn disable_file_logger() -> eyre::Result<()> {
if let Some(guard) = GUARD.get().ok_or_eyre("GUARD未初始化")?.lock().take() {
drop(guard);
@@ -115,6 +118,7 @@ pub fn disable_file_logger() -> eyre::Result<()> {
Ok(())
}
#[instrument(level = "error", skip_all)]
fn create_file_layer<S>(
app: &AppHandle,
) -> eyre::Result<(Box<dyn Layer<S> + Send + Sync>, Option<WorkerGuard>)>
@@ -149,17 +153,21 @@ where
Ok((Box::new(file_layer), Some(guard)))
}
#[instrument(level = "error", skip_all)]
async fn file_log_watcher(app: AppHandle) {
let (sender, mut receiver) = tokio::sync::mpsc::channel(1);
let event_handler_span = tracing::error_span!("file_log_watcher_event_handler");
let event_handler = move |res| {
tauri::async_runtime::block_on(async {
let send_event_task = async {
if let Err(err) = sender.send(res).await.map_err(eyre::Report::from) {
let err_title = "发送日志文件watcher事件失败";
let message = err.to_message();
tracing::error!(err_title, message);
}
});
};
tauri::async_runtime::block_on(send_event_task.instrument(event_handler_span.clone()));
};
let mut watcher = match RecommendedWatcher::new(event_handler, notify::Config::default())
@@ -214,6 +222,7 @@ async fn file_log_watcher(app: AppHandle) {
}
}
#[instrument(level = "error", skip_all)]
pub fn logs_dir(app: &AppHandle) -> eyre::Result<std::path::PathBuf> {
let app_data_dir = app
.path()
+5 -3
View File
@@ -1,6 +1,7 @@
use eyre::{OptionExt, eyre};
use serde::{Deserialize, Serialize};
use specta::Type;
use tracing::instrument;
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
#[serde(default)]
@@ -55,6 +56,7 @@ pub struct BangumiInfo {
impl BangumiInfo {
#[allow(clippy::cast_possible_wrap)]
#[instrument(level = "error", skip_all)]
pub fn get_episode_with_order(&self, ep_id: i64) -> eyre::Result<(&EpInBangumi, i64)> {
let episode_with_order = self
.episodes
@@ -69,19 +71,19 @@ impl BangumiInfo {
} else {
// 如果在正片中没有找到对应的ep_id,则在section中查找
let Some(sections) = &self.section else {
return Err(eyre!("找不到对应的ep_id为`{ep_id}`的番剧"));
return Err(eyre!("section为None"));
};
let section_index = sections
.iter()
.position(|s| s.episodes.iter().any(|e| e.id == ep_id))
.ok_or_eyre(format!("找不到含有ep_id为`{ep_id}`的ep的section"))?;
.ok_or_eyre("找不到含有对应ep_id的section")?;
sections[section_index]
.episodes
.iter()
.enumerate()
.map(|(i, e)| (e, i as i64 + 1))
.find(|(e, _)| e.id == ep_id)
.ok_or_eyre(format!("在section中找不到ep_id为`{ep_id}`的ep"))?
.ok_or_eyre("在section中找不到ep_id对应的ep")?
};
Ok(episode_with_order)
@@ -6,3 +6,19 @@ pub enum GetBangumiInfoParams {
EpId(i64),
SeasonId(i64),
}
impl GetBangumiInfoParams {
pub fn get_ep_id(&self) -> Option<i64> {
match self {
Self::EpId(ep_id) => Some(*ep_id),
Self::SeasonId(_) => None,
}
}
pub fn get_season_id(&self) -> Option<i64> {
match self {
Self::EpId(_) => None,
Self::SeasonId(season_id) => Some(*season_id),
}
}
}
@@ -6,3 +6,19 @@ pub enum GetCheeseInfoParams {
EpId(i64),
SeasonId(i64),
}
impl GetCheeseInfoParams {
pub fn get_ep_id(&self) -> Option<i64> {
match self {
Self::EpId(ep_id) => Some(*ep_id),
Self::SeasonId(_) => None,
}
}
pub fn get_season_id(&self) -> Option<i64> {
match self {
Self::EpId(_) => None,
Self::SeasonId(season_id) => Some(*season_id),
}
}
}
@@ -6,3 +6,19 @@ pub enum GetNormalInfoParams {
Bvid(String),
Aid(i64),
}
impl GetNormalInfoParams {
pub fn get_bvid(&self) -> Option<String> {
match self {
Self::Bvid(bvid) => Some(bvid.clone()),
Self::Aid(_) => None,
}
}
pub fn get_aid(&self) -> Option<i64> {
match self {
Self::Bvid(_) => None,
Self::Aid(aid) => Some(*aid),
}
}
}
+4
View File
@@ -6,6 +6,7 @@ use std::{
use byteorder::{BigEndian, ReadBytesExt};
use eyre::{OptionExt, WrapErr, eyre};
use tracing::instrument;
use crate::{
danmaku_xml_to_ass::{DamakuXmlDTag, DanmakuXmlITag},
@@ -48,6 +49,7 @@ impl From<u32> for BoxSizeField {
}
}
#[instrument(level = "error", skip_all, fields(file_path = ?file_path))]
pub fn is_mp4_complete(file_path: &Path) -> eyre::Result<bool> {
let file = File::open(file_path).wrap_err(format!("打开文件`{}`失败", file_path.display()))?;
let real_size = file
@@ -135,6 +137,7 @@ pub trait ToXml {
}
impl ToXml for Vec<DmSegMobileReply> {
#[instrument(level = "error", skip_all, fields(cid = cid))]
fn to_xml(&self, cid: i64) -> eyre::Result<String> {
let elems = self
.iter()
@@ -177,6 +180,7 @@ pub fn seconds_to_srt_time(seconds: f64) -> String {
format!("{h:02}:{m:02}:{s:02},{ms:03}")
}
#[instrument(level = "error", skip_all)]
pub fn get_ffmpeg_program() -> eyre::Result<PathBuf> {
let ffmpeg_program = std::env::current_exe()
.wrap_err("获取当前可执行文件路径失败")?
+4 -1
View File
@@ -3,6 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use eyre::{OptionExt, WrapErr, eyre};
use md5::{Digest, Md5};
use serde::Deserialize;
use tracing::instrument;
use crate::bili_client::{BiliClient, BiliResp};
@@ -25,7 +26,8 @@ struct WeiRespData {
impl BiliClient {
// 为请求参数进行 wbi 签名
pub(crate) async fn wbi(&self, params: &mut Vec<(&str, String)>) -> eyre::Result<()> {
#[instrument(level = "error", skip_all)]
pub async fn wbi(&self, params: &mut Vec<(&str, String)>) -> eyre::Result<()> {
let (img_key, sub_key) = self.get_wbi_keys().await.wrap_err("获取wbi keys失败")?;
let mixin_key = get_mixin_key((img_key + &sub_key).as_bytes());
@@ -46,6 +48,7 @@ impl BiliClient {
Ok(())
}
#[instrument(level = "error", skip_all)]
async fn get_wbi_keys(&self) -> eyre::Result<(String, String)> {
let request = self
.api_client