mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-09-05 07:27:42 +08:00
feat: 给日志和错误信息补充丰富的SpanTrace
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(¶ms);
|
||||
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(¶ms);
|
||||
@@ -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,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
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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 = ¶ms.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"));
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 ¶ms.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, ¶ms.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 ¶ms.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, ¶ms.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 ¶ms.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, ¶ms.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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("获取当前可执行文件路径失败")?
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user