refactor: 用eyre替换anyhow

This commit is contained in:
lanyeeee
2026-03-02 04:21:12 +08:00
parent 6ebdc83104
commit 5478edc770
28 changed files with 439 additions and 429 deletions
+17 -1
View File
@@ -285,11 +285,11 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
name = "bilibili-video-downloader" name = "bilibili-video-downloader"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"base64 0.22.1", "base64 0.22.1",
"byteorder", "byteorder",
"bytes", "bytes",
"chrono", "chrono",
"eyre",
"float-ord", "float-ord",
"fs4", "fs4",
"md-5", "md-5",
@@ -1007,6 +1007,16 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "eyre"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
dependencies = [
"indenter",
"once_cell",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.3.0" version = "2.3.0"
@@ -1850,6 +1860,12 @@ dependencies = [
"icu_properties", "icu_properties",
] ]
[[package]]
name = "indenter"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
+2 -1
View File
@@ -35,7 +35,7 @@ reqwest = { version = "0.12.22", default-features = false, features = ["default-
reqwest-retry = { version = "0.7.0" } reqwest-retry = { version = "0.7.0" }
reqwest-middleware = { version = "0.4.2" } reqwest-middleware = { version = "0.4.2" }
anyhow = { version = "1.0.98" } eyre = { version = "0.6.12" }
parking_lot = { version = "0.12.4", features = ["send_guard"] } parking_lot = { version = "0.12.4", features = ["send_guard"] }
tracing = { version = "0.1.41" } tracing = { version = "0.1.41" }
tracing-subscriber = { version = "0.3.19", features = ["json", "time", "local-time"] } tracing-subscriber = { version = "0.3.19", features = ["json", "time", "local-time"] }
@@ -62,3 +62,4 @@ strip = true
lto = true lto = true
codegen-units = 1 codegen-units = 1
panic = "abort" panic = "abort"
+145 -149
View File
@@ -1,8 +1,8 @@
use std::time::Duration; use std::time::Duration;
use anyhow::{Context, anyhow};
use base64::{Engine, engine::general_purpose}; use base64::{Engine, engine::general_purpose};
use bytes::Bytes; use bytes::Bytes;
use eyre::{OptionExt, WrapErr, eyre};
use parking_lot::RwLock; use parking_lot::RwLock;
use prost::Message; use prost::Message;
use reqwest::{Client, StatusCode}; use reqwest::{Client, StatusCode};
@@ -18,7 +18,7 @@ use tokio::task::JoinSet;
use crate::{ use crate::{
config::ProxyMode, config::ProxyMode,
extensions::{AnyhowErrorToStringChain, AppHandleExt}, extensions::{AppHandleExt, EyreToStringChain},
protobuf::DmSegMobileReply, protobuf::DmSegMobileReply,
types::{ types::{
bangumi_follow_info::BangumiFollowInfo, bangumi_info::BangumiInfo, bangumi_follow_info::BangumiFollowInfo, bangumi_info::BangumiInfo,
@@ -74,7 +74,7 @@ impl BiliClient {
*self.content_length_client.write() = content_length_client; *self.content_length_client.write() = content_length_client;
} }
pub async fn generate_qrcode(&self) -> anyhow::Result<QrcodeData> { pub async fn generate_qrcode(&self) -> eyre::Result<QrcodeData> {
// 发送生成二维码请求 // 发送生成二维码请求
let request = self let request = self
.api_client .api_client
@@ -85,28 +85,28 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为二维码数据 // 尝试将data解析为二维码数据
let data_str = data.to_string(); let data_str = data.to_string();
let qrcode_data: QrcodeData = serde_json::from_str(&data_str) let qrcode_data: QrcodeData = serde_json::from_str(&data_str)
.context(format!("将data解析为QrcodeData失败: {data_str}"))?; .wrap_err(format!("将data解析为QrcodeData失败: {data_str}"))?;
Ok(qrcode_data) Ok(qrcode_data)
} }
pub async fn get_qrcode_status(&self, qrcode_key: &str) -> anyhow::Result<QrcodeStatus> { pub async fn get_qrcode_status(&self, qrcode_key: &str) -> eyre::Result<QrcodeStatus> {
// 发送获取二维码状态请求 // 发送获取二维码状态请求
let params = json!({"qrcode_key": qrcode_key}); let params = json!({"qrcode_key": qrcode_key});
let request = self let request = self
@@ -119,30 +119,30 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为二维码状态 // 尝试将data解析为二维码状态
let data_str = data.to_string(); let data_str = data.to_string();
let qrcode_status: QrcodeStatus = serde_json::from_str(&data_str) let qrcode_status: QrcodeStatus = serde_json::from_str(&data_str)
.context(format!("将data解析为QrcodeStatus失败: {data_str}"))?; .wrap_err(format!("将data解析为QrcodeStatus失败: {data_str}"))?;
if ![0, 86101, 86090, 86038].contains(&qrcode_status.code) { if ![0, 86101, 86090, 86038].contains(&qrcode_status.code) {
return Err(anyhow!("预料之外的二维码code: {qrcode_status:?}")); return Err(eyre!("预料之外的二维码code: {qrcode_status:?}"));
} }
Ok(qrcode_status) Ok(qrcode_status)
} }
pub async fn get_user_info(&self, sessdata: &str) -> anyhow::Result<UserInfo> { pub async fn get_user_info(&self, sessdata: &str) -> eyre::Result<UserInfo> {
// 发送获取用户信息的请求 // 发送获取用户信息的请求
let request = self let request = self
.api_client .api_client
@@ -154,30 +154,30 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code == -101 { if bili_resp.code == -101 {
return Err(anyhow!("cookie错误或已过期,请重新登录: {bili_resp:?}")); return Err(eyre!("cookie错误或已过期,请重新登录: {bili_resp:?}"));
} else if bili_resp.code != 0 { } else if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为UserInfo // 尝试将data解析为UserInfo
let data_str = data.to_string(); let data_str = data.to_string();
let user_info: UserInfo = serde_json::from_str(&data_str) let user_info: UserInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为UserInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为UserInfo失败: {data_str}"))?;
Ok(user_info) Ok(user_info)
} }
pub async fn get_normal_info(&self, params: GetNormalInfoParams) -> anyhow::Result<NormalInfo> { pub async fn get_normal_info(&self, params: GetNormalInfoParams) -> eyre::Result<NormalInfo> {
use GetNormalInfoParams::{Aid, Bvid}; use GetNormalInfoParams::{Aid, Bvid};
let params = match params { let params = match params {
Bvid(bvid) => json!({"bvid": bvid}), Bvid(bvid) => json!({"bvid": bvid}),
@@ -195,23 +195,23 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为NormalInfo // 尝试将data解析为NormalInfo
let data_str = data.to_string(); let data_str = data.to_string();
let normal_info: NormalInfo = serde_json::from_str(&data_str) let normal_info: NormalInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为NormalInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为NormalInfo失败: {data_str}"))?;
Ok(normal_info) Ok(normal_info)
} }
@@ -219,7 +219,7 @@ impl BiliClient {
pub async fn get_bangumi_info( pub async fn get_bangumi_info(
&self, &self,
params: GetBangumiInfoParams, params: GetBangumiInfoParams,
) -> anyhow::Result<BangumiInfo> { ) -> eyre::Result<BangumiInfo> {
use GetBangumiInfoParams::{EpId, SeasonId}; use GetBangumiInfoParams::{EpId, SeasonId};
let params = match params { let params = match params {
EpId(ep_id) => json!({"ep_id": ep_id}), EpId(ep_id) => json!({"ep_id": ep_id}),
@@ -237,28 +237,28 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为BangumiInfo // 尝试将data解析为BangumiInfo
let data_str = data.to_string(); let data_str = data.to_string();
let bangumi_info: BangumiInfo = serde_json::from_str(&data_str) let bangumi_info: BangumiInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为BangumiInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为BangumiInfo失败: {data_str}"))?;
Ok(bangumi_info) Ok(bangumi_info)
} }
pub async fn get_cheese_info(&self, params: GetCheeseInfoParams) -> anyhow::Result<CheeseInfo> { pub async fn get_cheese_info(&self, params: GetCheeseInfoParams) -> eyre::Result<CheeseInfo> {
use GetCheeseInfoParams::{EpId, SeasonId}; use GetCheeseInfoParams::{EpId, SeasonId};
let params = match params { let params = match params {
EpId(ep_id) => json!({"ep_id": ep_id}), EpId(ep_id) => json!({"ep_id": ep_id}),
@@ -276,23 +276,23 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为CheeseInfo // 尝试将data解析为CheeseInfo
let data_str = data.to_string(); let data_str = data.to_string();
let cheese_info: CheeseInfo = serde_json::from_str(&data_str) let cheese_info: CheeseInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为CheeseInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为CheeseInfo失败: {data_str}"))?;
Ok(cheese_info) Ok(cheese_info)
} }
@@ -300,7 +300,7 @@ impl BiliClient {
pub async fn get_user_video_info( pub async fn get_user_video_info(
&self, &self,
params: GetUserVideoInfoParams, params: GetUserVideoInfoParams,
) -> anyhow::Result<UserVideoInfo> { ) -> eyre::Result<UserVideoInfo> {
const DM_IMG_INTER: &str = r#"{"ds":[],"wh":[0,0,0],"of":[0,0,0]}"#; const DM_IMG_INTER: &str = r#"{"ds":[],"wh":[0,0,0],"of":[0,0,0]}"#;
fn random_base64() -> String { fn random_base64() -> String {
@@ -337,28 +337,28 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为UserVideoInfo // 尝试将data解析为UserVideoInfo
let data_str = data.to_string(); let data_str = data.to_string();
let user_video_info: UserVideoInfo = serde_json::from_str(&data_str) let user_video_info: UserVideoInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为UserVideoInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为UserVideoInfo失败: {data_str}"))?;
Ok(user_video_info) Ok(user_video_info)
} }
pub async fn get_normal_url(&self, bvid: &str, cid: i64) -> anyhow::Result<NormalMediaUrl> { pub async fn get_normal_url(&self, bvid: &str, cid: i64) -> eyre::Result<NormalMediaUrl> {
let params = json!({ let params = json!({
"bvid": bvid, "bvid": bvid,
"cid": cid, "cid": cid,
@@ -377,28 +377,28 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为NormalMediaUrl // 尝试将data解析为NormalMediaUrl
let data_str = data.to_string(); let data_str = data.to_string();
let media_url: NormalMediaUrl = serde_json::from_str(&data_str) let media_url: NormalMediaUrl = serde_json::from_str(&data_str)
.context(format!("将data解析为NormalMediaUrl失败: {data_str}"))?; .wrap_err(format!("将data解析为NormalMediaUrl失败: {data_str}"))?;
Ok(media_url) Ok(media_url)
} }
pub async fn get_bangumi_url(&self, cid: i64) -> anyhow::Result<BangumiMediaUrl> { pub async fn get_bangumi_url(&self, cid: i64) -> eyre::Result<BangumiMediaUrl> {
let media_url_v2 = self.get_bangumi_url_v2(cid).await?; let media_url_v2 = self.get_bangumi_url_v2(cid).await?;
if media_url_v2.video_info.is_drm { if media_url_v2.video_info.is_drm {
self.get_bangumi_url_v1(cid).await self.get_bangumi_url_v1(cid).await
@@ -407,7 +407,7 @@ impl BiliClient {
} }
} }
async fn get_bangumi_url_v1(&self, cid: i64) -> anyhow::Result<BangumiMediaUrl> { async fn get_bangumi_url_v1(&self, cid: i64) -> eyre::Result<BangumiMediaUrl> {
let params = json!({ let params = json!({
"cid": cid, "cid": cid,
"qn": 127, "qn": 127,
@@ -426,32 +426,30 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code == -10403 { if bili_resp.code == -10403 {
return Err(anyhow!( return Err(eyre!("地区限制,请使用代理或切换线路后重试: {bili_resp:?}"));
"地区限制,请使用代理或切换线路后重试: {bili_resp:?}"
));
} else if bili_resp.code != 0 { } else if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为BangumiMediaUrl // 尝试将data解析为BangumiMediaUrl
let data_str = data.to_string(); let data_str = data.to_string();
let media_url: BangumiMediaUrl = serde_json::from_str(&data_str) let media_url: BangumiMediaUrl = serde_json::from_str(&data_str)
.context(format!("将data解析为BangumiMediaUrl失败: {data_str}"))?; .wrap_err(format!("将data解析为BangumiMediaUrl失败: {data_str}"))?;
Ok(media_url) Ok(media_url)
} }
async fn get_bangumi_url_v2(&self, cid: i64) -> anyhow::Result<BangumiMediaUrlV2> { async fn get_bangumi_url_v2(&self, cid: i64) -> eyre::Result<BangumiMediaUrlV2> {
let params = json!({ let params = json!({
"cid": cid, "cid": cid,
"qn": 127, "qn": 127,
@@ -471,32 +469,30 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code == -10403 { if bili_resp.code == -10403 {
return Err(anyhow!( return Err(eyre!("地区限制,请使用代理或切换线路后重试: {bili_resp:?}"));
"地区限制,请使用代理或切换线路后重试: {bili_resp:?}"
));
} else if bili_resp.code != 0 { } else if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为BangumiMediaUrlV2 // 尝试将data解析为BangumiMediaUrlV2
let data_str = data.to_string(); let data_str = data.to_string();
let media_url: BangumiMediaUrlV2 = serde_json::from_str(&data_str) let media_url: BangumiMediaUrlV2 = serde_json::from_str(&data_str)
.context(format!("将data解析为BangumiMediaUrlV2失败: {data_str}"))?; .wrap_err(format!("将data解析为BangumiMediaUrlV2失败: {data_str}"))?;
Ok(media_url) Ok(media_url)
} }
pub async fn get_cheese_url(&self, ep_id: i64) -> anyhow::Result<CheeseMediaUrl> { pub async fn get_cheese_url(&self, ep_id: i64) -> eyre::Result<CheeseMediaUrl> {
let params = json!({ let params = json!({
"ep_id": ep_id, "ep_id": ep_id,
"qn": 127, "qn": 127,
@@ -515,30 +511,30 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code == -403 { if bili_resp.code == -403 {
return Err(anyhow!("没有观看权限,请先购买: {bili_resp:?}")); return Err(eyre!("没有观看权限,请先购买: {bili_resp:?}"));
} else if bili_resp.code != 0 { } else if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为CheeseMediaUrl // 尝试将data解析为CheeseMediaUrl
let data_str = data.to_string(); let data_str = data.to_string();
let media_url: CheeseMediaUrl = serde_json::from_str(&data_str) let media_url: CheeseMediaUrl = serde_json::from_str(&data_str)
.context(format!("将data解析为CheeseMediaUrl失败: {data_str}"))?; .wrap_err(format!("将data解析为CheeseMediaUrl失败: {data_str}"))?;
Ok(media_url) Ok(media_url)
} }
pub async fn get_player_info(&self, aid: i64, cid: i64) -> anyhow::Result<PlayerInfo> { pub async fn get_player_info(&self, aid: i64, cid: i64) -> eyre::Result<PlayerInfo> {
let params = json!({ let params = json!({
"aid": aid, "aid": aid,
"cid": cid, "cid": cid,
@@ -555,28 +551,28 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为PlayerInfo // 尝试将data解析为PlayerInfo
let data_str = data.to_string(); let data_str = data.to_string();
let player_info: PlayerInfo = serde_json::from_str(&data_str) let player_info: PlayerInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为PlayerInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为PlayerInfo失败: {data_str}"))?;
Ok(player_info) Ok(player_info)
} }
pub async fn get_fav_folders(&self, uid: i64) -> anyhow::Result<FavFolders> { pub async fn get_fav_folders(&self, uid: i64) -> eyre::Result<FavFolders> {
let params = json!({"up_mid": uid}); let params = json!({"up_mid": uid});
// 发送获取收藏夹信息的请求 // 发送获取收藏夹信息的请求
let request = self let request = self
@@ -590,28 +586,28 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为FavFolders // 尝试将data解析为FavFolders
let data_str = data.to_string(); let data_str = data.to_string();
let fav_folders: FavFolders = serde_json::from_str(&data_str) let fav_folders: FavFolders = serde_json::from_str(&data_str)
.context(format!("将data解析为FavFolders失败: {data_str}"))?; .wrap_err(format!("将data解析为FavFolders失败: {data_str}"))?;
Ok(fav_folders) Ok(fav_folders)
} }
pub async fn get_fav_info(&self, params: GetFavInfoParams) -> anyhow::Result<FavInfo> { pub async fn get_fav_info(&self, params: GetFavInfoParams) -> eyre::Result<FavInfo> {
let params = json!({ let params = json!({
"media_id": params.media_list_id, "media_id": params.media_list_id,
"pn": params.pn, "pn": params.pn,
@@ -630,28 +626,28 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为FavInfo // 尝试将data解析为FavInfo
let data_str = data.to_string(); let data_str = data.to_string();
let fav_info: FavInfo = serde_json::from_str(&data_str) let fav_info: FavInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为FavInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为FavInfo失败: {data_str}"))?;
Ok(fav_info) Ok(fav_info)
} }
pub async fn get_watch_later_info(&self, page: i32) -> anyhow::Result<WatchLaterInfo> { pub async fn get_watch_later_info(&self, page: i32) -> eyre::Result<WatchLaterInfo> {
// 发送获取稍后观看信息的请求 // 发送获取稍后观看信息的请求
let params = json!({"ps": 20, "pn": page}); let params = json!({"ps": 20, "pn": page});
let request = self let request = self
@@ -665,23 +661,23 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为WatchLaterInfo // 尝试将data解析为WatchLaterInfo
let data_str = data.to_string(); let data_str = data.to_string();
let watch_later_info: WatchLaterInfo = serde_json::from_str(&data_str) let watch_later_info: WatchLaterInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为WatchLaterInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为WatchLaterInfo失败: {data_str}"))?;
Ok(watch_later_info) Ok(watch_later_info)
} }
@@ -689,7 +685,7 @@ impl BiliClient {
pub async fn get_bangumi_follow_info( pub async fn get_bangumi_follow_info(
&self, &self,
params: GetBangumiFollowInfoParams, params: GetBangumiFollowInfoParams,
) -> anyhow::Result<BangumiFollowInfo> { ) -> eyre::Result<BangumiFollowInfo> {
// 发送获取番剧追踪信息的请求 // 发送获取番剧追踪信息的请求
let params = json!({ let params = json!({
"vmid": params.vmid, "vmid": params.vmid,
@@ -709,23 +705,23 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为BangumiFollowInfo // 尝试将data解析为BangumiFollowInfo
let data_str = data.to_string(); let data_str = data.to_string();
let bangumi_follow_info: BangumiFollowInfo = serde_json::from_str(&data_str) let bangumi_follow_info: BangumiFollowInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为BangumiFollowInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为BangumiFollowInfo失败: {data_str}"))?;
Ok(bangumi_follow_info) Ok(bangumi_follow_info)
} }
@@ -733,7 +729,7 @@ impl BiliClient {
pub async fn get_history_info( pub async fn get_history_info(
&self, &self,
params: GetHistoryInfoParams, params: GetHistoryInfoParams,
) -> anyhow::Result<HistoryInfo> { ) -> eyre::Result<HistoryInfo> {
let device_type: i64 = params.device_type.into(); let device_type: i64 = params.device_type.into();
let params = json!({ let params = json!({
"pn": params.pn, "pn": params.pn,
@@ -756,23 +752,23 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为HistoryInfo // 尝试将data解析为HistoryInfo
let data_str = data.to_string(); let data_str = data.to_string();
let history_info: HistoryInfo = serde_json::from_str(&data_str) let history_info: HistoryInfo = serde_json::from_str(&data_str)
.context(format!("将data解析为HistoryInfo失败: {data_str}"))?; .wrap_err(format!("将data解析为HistoryInfo失败: {data_str}"))?;
Ok(history_info) Ok(history_info)
} }
@@ -782,7 +778,7 @@ impl BiliClient {
media_url: &str, media_url: &str,
start: u64, start: u64,
end: u64, end: u64,
) -> anyhow::Result<Bytes> { ) -> eyre::Result<Bytes> {
let request = self let request = self
.media_client .media_client
.read() .read()
@@ -792,7 +788,7 @@ impl BiliClient {
// 检查http响应状态码 // 检查http响应状态码
let status = http_resp.status(); let status = http_resp.status();
if status != StatusCode::PARTIAL_CONTENT { if status != StatusCode::PARTIAL_CONTENT {
return Err(anyhow!("预料之外的状态码({status})")); return Err(eyre!("预料之外的状态码({status})"));
} }
let bytes = http_resp.bytes().await?; let bytes = http_resp.bytes().await?;
@@ -800,32 +796,32 @@ impl BiliClient {
Ok(bytes) Ok(bytes)
} }
pub async fn get_content_length(&self, media_url: &str) -> anyhow::Result<u64> { pub async fn get_content_length(&self, media_url: &str) -> eyre::Result<u64> {
fn parse_content_length(headers: &HeaderMap) -> anyhow::Result<u64> { fn parse_content_length(headers: &HeaderMap) -> eyre::Result<u64> {
headers headers
.get("Content-Length") .get("Content-Length")
.context("缺少 Content-Length 响应头")? .ok_or_eyre("缺少 Content-Length 响应头")?
.to_str() .to_str()
.context("Content-Length 响应头无法转换为字符串")? .wrap_err("Content-Length 响应头无法转换为字符串")?
.parse::<u64>() .parse::<u64>()
.context("Content-Length 响应头无法转换为整数") .wrap_err("Content-Length 响应头无法转换为整数")
} }
fn parse_total_from_content_range(headers: &HeaderMap) -> anyhow::Result<u64> { fn parse_total_from_content_range(headers: &HeaderMap) -> eyre::Result<u64> {
// Example: "bytes 0-0/12345" // Example: "bytes 0-0/12345"
let content_range = headers let content_range = headers
.get("Content-Range") .get("Content-Range")
.context("缺少 Content-Range 响应头")? .ok_or_eyre("缺少 Content-Range 响应头")?
.to_str() .to_str()
.context("Content-Range 响应头无法转换为字符串")?; .wrap_err("Content-Range 响应头无法转换为字符串")?;
let Some((_, total)) = content_range.split_once('/') else { let Some((_, total)) = content_range.split_once('/') else {
return Err(anyhow!("预料之外的 Content-Range 格式: {content_range}")); return Err(eyre!("预料之外的 Content-Range 格式: {content_range}"));
}; };
total total
.parse::<u64>() .parse::<u64>()
.context("Content-Range 总大小无法转换为整数") .wrap_err("Content-Range 总大小无法转换为整数")
} }
// 优先使用 HEAD 获取 Content-Length // 优先使用 HEAD 获取 Content-Length
@@ -855,7 +851,7 @@ impl BiliClient {
return parse_content_length(http_resp.headers()); return parse_content_length(http_resp.headers());
} }
Err(anyhow!("预料之外的状态码({status})")) Err(eyre!("预料之外的状态码({status})"))
} }
pub async fn get_url_with_content_length(&self, urls: Vec<String>) -> Vec<(String, u64)> { pub async fn get_url_with_content_length(&self, urls: Vec<String>) -> Vec<(String, u64)> {
@@ -889,7 +885,7 @@ impl BiliClient {
aid: i64, aid: i64,
cid: i64, cid: i64,
duration: u64, duration: u64,
) -> anyhow::Result<Vec<DmSegMobileReply>> { ) -> eyre::Result<Vec<DmSegMobileReply>> {
let client = self.api_client.read().clone(); let client = self.api_client.read().clone();
// 以6分钟为单位分段 // 以6分钟为单位分段
let segment_count = duration.div_ceil(360); let segment_count = duration.div_ceil(360);
@@ -916,11 +912,11 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
if status != StatusCode::OK { if status != StatusCode::OK {
let body = http_resp.text().await?; let body = http_resp.text().await?;
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
let body = http_resp.bytes().await?; let body = http_resp.bytes().await?;
let reply = let reply =
DmSegMobileReply::decode(body).context("将body解析为DmSegMobileReply失败")?; DmSegMobileReply::decode(body).wrap_err("将body解析为DmSegMobileReply失败")?;
Ok(reply) Ok(reply)
}); });
@@ -939,37 +935,37 @@ impl BiliClient {
Ok(replies) Ok(replies)
} }
pub async fn get_subtitle(&self, url: &str) -> anyhow::Result<Subtitle> { pub async fn get_subtitle(&self, url: &str) -> eyre::Result<Subtitle> {
let request = self.api_client.read().get(url); let request = self.api_client.read().get(url);
let http_resp = request.send().await?; let http_resp = request.send().await?;
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为Subtitle // 尝试将body解析为Subtitle
let subtitle: Subtitle = let subtitle: Subtitle =
serde_json::from_str(&body).context(format!("将body解析为Subtitle失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为Subtitle失败: {body}"))?;
Ok(subtitle) Ok(subtitle)
} }
pub async fn get_cover_data_and_ext(&self, url: &str) -> anyhow::Result<(Bytes, String)> { pub async fn get_cover_data_and_ext(&self, url: &str) -> eyre::Result<(Bytes, String)> {
let request = self.api_client.read().get(url); let request = self.api_client.read().get(url);
let http_resp = request.send().await?; let http_resp = request.send().await?;
// 检查http响应状态码 // 检查http响应状态码
let status = http_resp.status(); let status = http_resp.status();
if status != StatusCode::OK { if status != StatusCode::OK {
let body = http_resp.text().await?; let body = http_resp.text().await?;
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
let content_type = http_resp let content_type = http_resp
.headers() .headers()
.get("Content-Type") .get("Content-Type")
.context("缺少 Content-Type 响应头")? .ok_or_eyre("缺少 Content-Type 响应头")?
.to_str() .to_str()
.context("Content-Type 响应头无法转换为字符串")? .wrap_err("Content-Type 响应头无法转换为字符串")?
.to_string(); .to_string();
let ext = match content_type.as_str() { let ext = match content_type.as_str() {
@@ -984,7 +980,7 @@ impl BiliClient {
Ok((bytes, ext.to_string())) Ok((bytes, ext.to_string()))
} }
pub async fn get_tags(&self, aid: i64) -> anyhow::Result<Tags> { pub async fn get_tags(&self, aid: i64) -> eyre::Result<Tags> {
// 发送获取普通视频标签的请求 // 发送获取普通视频标签的请求
let params = json!({"aid": aid}); let params = json!({"aid": aid});
let request = self let request = self
@@ -998,23 +994,23 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != StatusCode::OK { if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的code字段 // 检查BiliResp的code字段
if bili_resp.code != 0 { if bili_resp.code != 0 {
return Err(anyhow!("预料之外的code: {bili_resp:?}")); return Err(eyre!("预料之外的code: {bili_resp:?}"));
} }
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为Tags // 尝试将data解析为Tags
let data_str = data.to_string(); let data_str = data.to_string();
let tags: Tags = let tags: Tags = serde_json::from_str(&data_str)
serde_json::from_str(&data_str).context(format!("将data解析为Tags失败: {data_str}"))?; .wrap_err(format!("将data解析为Tags失败: {data_str}"))?;
Ok(tags) Ok(tags)
} }
@@ -1023,7 +1019,7 @@ impl BiliClient {
&self, &self,
bvid: &str, bvid: &str,
cid: Option<i64>, cid: Option<i64>,
) -> anyhow::Result<SkipSegments> { ) -> eyre::Result<SkipSegments> {
// 发送获取跳过片段的请求 // 发送获取跳过片段的请求
let mut params = json!({ let mut params = json!({
"videoID": bvid, "videoID": bvid,
@@ -1045,11 +1041,11 @@ impl BiliClient {
if status == StatusCode::NOT_FOUND { if status == StatusCode::NOT_FOUND {
return Ok(SkipSegments(Vec::new())); return Ok(SkipSegments(Vec::new()));
} else if status != StatusCode::OK { } else if status != StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为SkipSegments // 尝试将body解析为SkipSegments
let skip_segments: SkipSegments = let skip_segments: SkipSegments = serde_json::from_str(&body)
serde_json::from_str(&body).context(format!("将body解析为SkipSegments失败: {body}"))?; .wrap_err(format!("将body解析为SkipSegments失败: {body}"))?;
Ok(skip_segments) Ok(skip_segments)
} }
@@ -1132,7 +1128,7 @@ impl ClientBuilderExt for reqwest::ClientBuilder {
let proxy_port = &config.proxy_port; let proxy_port = &config.proxy_port;
let proxy_url = format!("http://{proxy_host}:{proxy_port}"); let proxy_url = format!("http://{proxy_host}:{proxy_port}");
match reqwest::Proxy::all(&proxy_url).map_err(anyhow::Error::from) { match reqwest::Proxy::all(&proxy_url).map_err(eyre::Report::from) {
Ok(proxy) => self.proxy(proxy), Ok(proxy) => self.proxy(proxy),
Err(err) => { Err(err) => {
let err_title = format!("{client_name}将`{proxy_url}`设为代理失败,将直连"); let err_title = format!("{client_name}将`{proxy_url}`设为代理失败,将直连");
+4 -4
View File
@@ -1,4 +1,4 @@
use anyhow::Context; use eyre::WrapErr;
use parking_lot::RwLock; use parking_lot::RwLock;
use tauri::AppHandle; use tauri::AppHandle;
use tauri_plugin_opener::OpenerExt; use tauri_plugin_opener::OpenerExt;
@@ -366,10 +366,10 @@ pub async fn search(app: AppHandle, params: SearchParams) -> CommandResult<Searc
#[specta::specta] #[specta::specta]
pub fn get_logs_dir_size(app: AppHandle) -> CommandResult<u64> { pub fn get_logs_dir_size(app: AppHandle) -> CommandResult<u64> {
let logs_dir = logger::logs_dir(&app) let logs_dir = logger::logs_dir(&app)
.context("获取日志目录失败") .wrap_err("获取日志目录失败")
.map_err(|err| CommandError::from("获取日志目录大小失败", err))?; .map_err(|err| CommandError::from("获取日志目录大小失败", err))?;
let logs_dir_size = std::fs::read_dir(&logs_dir) let logs_dir_size = std::fs::read_dir(&logs_dir)
.context(format!("读取日志目录`{}`失败", logs_dir.display())) .wrap_err(format!("读取日志目录`{}`失败", logs_dir.display()))
.map_err(|err| CommandError::from("获取日志目录大小失败", err))? .map_err(|err| CommandError::from("获取日志目录大小失败", err))?
.filter_map(Result::ok) .filter_map(Result::ok)
.filter_map(|entry| entry.metadata().ok()) .filter_map(|entry| entry.metadata().ok())
@@ -385,7 +385,7 @@ pub fn get_logs_dir_size(app: AppHandle) -> CommandResult<u64> {
pub fn show_path_in_file_manager(app: AppHandle, path: &str) -> CommandResult<()> { pub fn show_path_in_file_manager(app: AppHandle, path: &str) -> CommandResult<()> {
app.opener() app.opener()
.reveal_item_in_dir(path) .reveal_item_in_dir(path)
.context(format!("在文件管理器中打开`{path}`失败")) .wrap_err(format!("在文件管理器中打开`{path}`失败"))
.map_err(|err| CommandError::from("在文件管理器中打开失败", err))?; .map_err(|err| CommandError::from("在文件管理器中打开失败", err))?;
Ok(()) Ok(())
} }
+2 -2
View File
@@ -47,7 +47,7 @@ pub struct Config {
} }
impl Config { impl Config {
pub fn new(app: &AppHandle) -> anyhow::Result<Config> { pub fn new(app: &AppHandle) -> eyre::Result<Config> {
let app_data_dir = app.path().app_data_dir()?; let app_data_dir = app.path().app_data_dir()?;
let config_path = app_data_dir.join("config.json"); let config_path = app_data_dir.join("config.json");
@@ -67,7 +67,7 @@ impl Config {
Ok(config) Ok(config)
} }
pub fn save(&self, app: &AppHandle) -> anyhow::Result<()> { pub fn save(&self, app: &AppHandle) -> eyre::Result<()> {
let app_data_dir = app.path().app_data_dir()?; let app_data_dir = app.path().app_data_dir()?;
let config_path = app_data_dir.join("config.json"); let config_path = app_data_dir.join("config.json");
let config_string = serde_json::to_string_pretty(self)?; let config_string = serde_json::to_string_pretty(self)?;
@@ -1,4 +1,4 @@
use anyhow::Result; use eyre::Result;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt; use std::fmt;
use std::io::{BufWriter, Write}; use std::io::{BufWriter, Write};
+11 -11
View File
@@ -5,10 +5,10 @@ pub mod drawable;
use std::{cmp::Ordering, fs::File}; use std::{cmp::Ordering, fs::File};
use anyhow::anyhow;
use ass_writer::AssWriter; use ass_writer::AssWriter;
use canvas::CanvasConfig; use canvas::CanvasConfig;
use danmaku::{Danmaku, DanmakuType}; use danmaku::{Danmaku, DanmakuType};
use eyre::eyre;
use yaserde::{YaDeserialize, YaSerialize}; use yaserde::{YaDeserialize, YaSerialize};
#[derive(YaSerialize, YaDeserialize)] #[derive(YaSerialize, YaDeserialize)]
@@ -33,7 +33,7 @@ pub fn xml_to_ass(
ass_file: File, ass_file: File,
title: String, title: String,
config: CanvasConfig, config: CanvasConfig,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let mut writer = AssWriter::new(ass_file, title, config.clone())?; let mut writer = AssWriter::new(ass_file, title, config.clone())?;
let mut canvas = config.canvas(); let mut canvas = config.canvas();
@@ -54,24 +54,24 @@ pub fn xml_to_ass(
} }
trait ToDanmakuType { trait ToDanmakuType {
fn to_danmaku_type(&self) -> anyhow::Result<DanmakuType>; fn to_danmaku_type(&self) -> eyre::Result<DanmakuType>;
} }
impl ToDanmakuType for u32 { impl ToDanmakuType for u32 {
fn to_danmaku_type(&self) -> anyhow::Result<DanmakuType> { fn to_danmaku_type(&self) -> eyre::Result<DanmakuType> {
match self { match self {
1 => Ok(DanmakuType::Float), 1 => Ok(DanmakuType::Float),
4 => Ok(DanmakuType::Bottom), 4 => Ok(DanmakuType::Bottom),
5 => Ok(DanmakuType::Top), 5 => Ok(DanmakuType::Top),
6 => Ok(DanmakuType::Reverse), 6 => Ok(DanmakuType::Reverse),
_ => Err(anyhow!("未知的弹幕类型:{self}")), _ => Err(eyre!("未知的弹幕类型:{self}")),
} }
} }
} }
pub fn xml_to_danmakus(xml: &str) -> anyhow::Result<Vec<Danmaku>> { pub fn xml_to_danmakus(xml: &str) -> eyre::Result<Vec<Danmaku>> {
let xml = sanitize_xml(xml); let xml = sanitize_xml(xml);
let i_tag: DanmakuXmlITag = yaserde::de::from_str(&xml).map_err(|e| anyhow!(e))?; let i_tag: DanmakuXmlITag = yaserde::de::from_str(&xml).map_err(|e| eyre!(e))?;
let mut danmakus = Vec::new(); let mut danmakus = Vec::new();
@@ -83,7 +83,7 @@ pub fn xml_to_danmakus(xml: &str) -> anyhow::Result<Vec<Danmaku>> {
let mut p_attr = elem.p.split(','); let mut p_attr = elem.p.split(',');
let Some(timeline_s) = p_attr.next().and_then(|s| s.parse::<f64>().ok()) else { let Some(timeline_s) = p_attr.next().and_then(|s| s.parse::<f64>().ok()) else {
return Err(anyhow!("弹幕`{content}`的p属性中没有时间")); return Err(eyre!("弹幕`{content}`的p属性中没有时间"));
}; };
let Some(r#type) = p_attr let Some(r#type) = p_attr
@@ -91,15 +91,15 @@ pub fn xml_to_danmakus(xml: &str) -> anyhow::Result<Vec<Danmaku>> {
.and_then(|s| s.parse::<u32>().ok()) .and_then(|s| s.parse::<u32>().ok())
.and_then(|num| num.to_danmaku_type().ok()) .and_then(|num| num.to_danmaku_type().ok())
else { else {
return Err(anyhow!("弹幕`{content}`的p属性中没有弹幕类型")); return Err(eyre!("弹幕`{content}`的p属性中没有弹幕类型"));
}; };
let Some(fontsize) = p_attr.next().and_then(|s| s.parse::<u32>().ok()) else { let Some(fontsize) = p_attr.next().and_then(|s| s.parse::<u32>().ok()) else {
return Err(anyhow!("弹幕`{content}`的p属性中没有字体大小")); return Err(eyre!("弹幕`{content}`的p属性中没有字体大小"));
}; };
let Some(rgb) = p_attr.next().and_then(|s| s.parse::<u32>().ok()) else { let Some(rgb) = p_attr.next().and_then(|s| s.parse::<u32>().ok()) else {
return Err(anyhow!("弹幕`{content}`的p属性中没有颜色")); return Err(eyre!("弹幕`{content}`的p属性中没有颜色"));
}; };
// rgb 是个数字,类似 0x010203 // rgb 是个数字,类似 0x010203
@@ -23,7 +23,7 @@ pub struct DownloadChunkTask {
} }
impl DownloadChunkTask { impl DownloadChunkTask {
pub async fn process(self) -> anyhow::Result<usize> { pub async fn process(self) -> eyre::Result<usize> {
let download_chunk_task = self.download_chunk(); let download_chunk_task = self.download_chunk();
tokio::pin!(download_chunk_task); tokio::pin!(download_chunk_task);
@@ -64,7 +64,7 @@ impl DownloadChunkTask {
} }
} }
async fn download_chunk(&self) -> anyhow::Result<usize> { async fn download_chunk(&self) -> eyre::Result<usize> {
let bili_client = self.download_task.app.get_bili_client(); let bili_client = self.download_task.app.get_bili_client();
let chunk_data = bili_client let chunk_data = bili_client
.get_media_chunk(&self.url, self.start, self.end) .get_media_chunk(&self.url, self.start, self.end)
@@ -97,7 +97,7 @@ impl DownloadChunkTask {
async fn acquire_chunk_permit<'a>( async fn acquire_chunk_permit<'a>(
&'a self, &'a self,
permit: &mut Option<SemaphorePermit<'a>>, permit: &mut Option<SemaphorePermit<'a>>,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
*permit = match permit.take() { *permit = match permit.take() {
// 如果有permit,则直接用 // 如果有permit,则直接用
Some(permit) => Some(permit), Some(permit) => Some(permit),
+12 -12
View File
@@ -8,7 +8,7 @@ use std::{
time::Duration, time::Duration,
}; };
use anyhow::Context; use eyre::WrapErr;
use parking_lot::RwLock; use parking_lot::RwLock;
use tauri::{AppHandle, Manager}; use tauri::{AppHandle, Manager};
use tauri_specta::Event; use tauri_specta::Event;
@@ -16,7 +16,7 @@ use tokio::sync::Semaphore;
use crate::{ use crate::{
events::DownloadEvent, events::DownloadEvent,
extensions::{AnyhowErrorToStringChain, AppHandleExt}, extensions::{AppHandleExt, EyreToStringChain},
types::{ types::{
create_download_task_params::CreateDownloadTaskParams, create_download_task_params::CreateDownloadTaskParams,
restart_download_task_params::RestartDownloadTaskParams, restart_download_task_params::RestartDownloadTaskParams,
@@ -59,10 +59,10 @@ impl DownloadManager {
manager manager
} }
pub fn restore_download_tasks(&self) -> anyhow::Result<()> { pub fn restore_download_tasks(&self) -> eyre::Result<()> {
let task_dir = self.get_task_dir()?; let task_dir = self.get_task_dir()?;
std::fs::create_dir_all(&task_dir) std::fs::create_dir_all(&task_dir)
.context(format!("创建下载任务目录`{}`失败", task_dir.display()))?; .wrap_err(format!("创建下载任务目录`{}`失败", task_dir.display()))?;
let mut tasks = self.download_tasks.write(); let mut tasks = self.download_tasks.write();
for entry in std::fs::read_dir(&task_dir)?.filter_map(Result::ok) { for entry in std::fs::read_dir(&task_dir)?.filter_map(Result::ok) {
@@ -150,9 +150,9 @@ impl DownloadManager {
continue; continue;
} }
if let Err(err) = task.delete_sender.send(()).map_err(anyhow::Error::from) { if let Err(err) = task.delete_sender.send(()).map_err(eyre::Report::from) {
let err_title = "删除下载任务失败"; let err_title = "删除下载任务失败";
let err = err.context(format!("通知ID为`{task_id}`的下载任务删除失败")); let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务删除失败"));
let string_chain = err.to_string_chain(); let string_chain = err.to_string_chain();
tracing::error!(err_title, message = string_chain); tracing::error!(err_title, message = string_chain);
tasks.insert(task_id.clone(), task); tasks.insert(task_id.clone(), task);
@@ -173,9 +173,9 @@ impl DownloadManager {
continue; continue;
}; };
if let Err(err) = task.restart_sender.send(()).map_err(anyhow::Error::from) { if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) {
let err_title = "重来下载任务失败"; let err_title = "重来下载任务失败";
let err = err.context(format!("通知ID为`{task_id}`的下载任务重来失败")); let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务重来失败"));
let string_chain = err.to_string_chain(); let string_chain = err.to_string_chain();
tracing::error!(err_title, message = string_chain); tracing::error!(err_title, message = string_chain);
continue; continue;
@@ -217,9 +217,9 @@ impl DownloadManager {
progress.audio_task.audio_quality = params.audio_quality; progress.audio_task.audio_quality = params.audio_quality;
} }
if let Err(err) = task.restart_sender.send(()).map_err(anyhow::Error::from) { if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) {
let err_title = "重来下载任务失败"; let err_title = "重来下载任务失败";
let err = err.context(format!("通知ID为`{task_id}`的下载任务重来失败")); let err = err.wrap_err(format!("通知ID为`{task_id}`的下载任务重来失败"));
let string_chain = err.to_string_chain(); let string_chain = err.to_string_chain();
tracing::error!(err_title, message = string_chain); tracing::error!(err_title, message = string_chain);
return; return;
@@ -241,13 +241,13 @@ impl DownloadManager {
} }
} }
fn get_task_dir(&self) -> anyhow::Result<PathBuf> { fn get_task_dir(&self) -> eyre::Result<PathBuf> {
let app_data_dir = self.app.path().app_data_dir()?; let app_data_dir = self.app.path().app_data_dir()?;
let task_dir = app_data_dir.join(".下载任务"); let task_dir = app_data_dir.join(".下载任务");
Ok(task_dir) Ok(task_dir)
} }
fn delete_progress_file(&self, task_id: &str) -> anyhow::Result<()> { fn delete_progress_file(&self, task_id: &str) -> eyre::Result<()> {
let task_dir = self.get_task_dir()?; let task_dir = self.get_task_dir()?;
let task_file = task_dir.join(format!("{task_id}.json")); let task_file = task_dir.join(format!("{task_id}.json"));
if task_file.exists() { if task_file.exists() {
+33 -33
View File
@@ -4,7 +4,7 @@ use std::{
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use anyhow::{Context, anyhow}; use eyre::{OptionExt, WrapErr, eyre};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
use tauri::{AppHandle, Manager}; use tauri::{AppHandle, Manager};
@@ -76,7 +76,7 @@ impl DownloadProgress {
info: &NormalInfo, info: &NormalInfo,
aid: i64, aid: i64,
cid: Option<i64>, cid: Option<i64>,
) -> anyhow::Result<Vec<Self>> { ) -> eyre::Result<Vec<Self>> {
let config = app.get_config().read().clone(); let config = app.get_config().read().clone();
if let Some(ugc_season) = &info.ugc_season { if let Some(ugc_season) = &info.ugc_season {
@@ -87,10 +87,10 @@ impl DownloadProgress {
} }
#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_possible_wrap)]
pub fn from_bangumi(app: &AppHandle, info: &BangumiInfo, ep_id: i64) -> anyhow::Result<Self> { 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 (episode, episode_order) = info.get_episode_with_order(ep_id)?;
let Some(duration) = episode.duration else { let Some(duration) = episode.duration else {
return Err(anyhow!("找不到ep_id为`{ep_id}`的番剧的时长")); return Err(eyre!("找不到ep_id为`{ep_id}`的番剧的时长"));
}; };
// 将毫秒转换为秒 // 将毫秒转换为秒
let duration = duration / 1000; let duration = duration / 1000;
@@ -147,12 +147,12 @@ impl DownloadProgress {
Ok(progress) Ok(progress)
} }
pub fn from_cheese(app: &AppHandle, info: &CheeseInfo, ep_id: i64) -> anyhow::Result<Self> { pub fn from_cheese(app: &AppHandle, info: &CheeseInfo, ep_id: i64) -> eyre::Result<Self> {
let episode = info let episode = info
.episodes .episodes
.iter() .iter()
.find(|ep| ep.id == ep_id) .find(|ep| ep.id == ep_id)
.context(format!("找不到ep_id为`{ep_id}`的课程"))?; .ok_or_eyre(format!("找不到ep_id为`{ep_id}`的课程"))?;
let config = app.get_config().read().clone(); let config = app.get_config().read().clone();
@@ -196,7 +196,7 @@ impl DownloadProgress {
Ok(progress) Ok(progress)
} }
pub async fn process(&mut self, download_task: &Arc<DownloadTask>) -> anyhow::Result<()> { pub async fn process(&mut self, download_task: &Arc<DownloadTask>) -> eyre::Result<()> {
let ids_string = self.get_ids_string(); let ids_string = self.get_ids_string();
let _ = DownloadEvent::ProgressPreparing { let _ = DownloadEvent::ProgressPreparing {
@@ -206,14 +206,14 @@ impl DownloadProgress {
self.prepare(&download_task.app) self.prepare(&download_task.app)
.await .await
.context("准备下载失败")?; .wrap_err("准备下载失败")?;
self.completed_ts = None; // 重置完成时间戳 self.completed_ts = None; // 重置完成时间戳
download_task.update_progress(|p| *p = self.clone()); download_task.update_progress(|p| *p = self.clone());
let (episode_dir, filename) = (&self.episode_dir, &self.filename); let (episode_dir, filename) = (&self.episode_dir, &self.filename);
std::fs::create_dir_all(episode_dir).context(format!( std::fs::create_dir_all(episode_dir).wrap_err(format!(
"{ids_string} 创建目录`{}`失败", "{ids_string} 创建目录`{}`失败",
episode_dir.display() episode_dir.display()
))?; ))?;
@@ -234,7 +234,7 @@ impl DownloadProgress {
video_task video_task
.process(download_task, self) .process(download_task, self)
.await .await
.context(format!("{ids_string} `{filename}`下载视频文件失败"))?; .wrap_err(format!("{ids_string} `{filename}`下载视频文件失败"))?;
tracing::debug!("{ids_string} `{filename}`视频下载任务完成"); tracing::debug!("{ids_string} `{filename}`视频下载任务完成");
} }
@@ -242,7 +242,7 @@ impl DownloadProgress {
audio_task audio_task
.process(download_task, self) .process(download_task, self)
.await .await
.context(format!("{ids_string} `{filename}`下载音频文件失败"))?; .wrap_err(format!("{ids_string} `{filename}`下载音频文件失败"))?;
tracing::debug!("{ids_string} `{filename}`音频下载任务完成"); tracing::debug!("{ids_string} `{filename}`音频下载任务完成");
} }
@@ -259,7 +259,7 @@ impl DownloadProgress {
video_process_task video_process_task
.process(download_task, self, &mut player_info) .process(download_task, self, &mut player_info)
.await .await
.context(format!("{ids_string} `{filename}`视频处理失败"))?; .wrap_err(format!("{ids_string} `{filename}`视频处理失败"))?;
tracing::debug!("{ids_string} `{filename}`视频处理任务完成"); tracing::debug!("{ids_string} `{filename}`视频处理任务完成");
} }
@@ -267,7 +267,7 @@ impl DownloadProgress {
danmaku_task danmaku_task
.process(download_task, self) .process(download_task, self)
.await .await
.context(format!("{ids_string} `{filename}`下载弹幕失败"))?; .wrap_err(format!("{ids_string} `{filename}`下载弹幕失败"))?;
tracing::debug!("{ids_string} `{filename}`弹幕下载任务完成"); tracing::debug!("{ids_string} `{filename}`弹幕下载任务完成");
} }
@@ -275,7 +275,7 @@ impl DownloadProgress {
subtitle_task subtitle_task
.process(download_task, self, &mut player_info) .process(download_task, self, &mut player_info)
.await .await
.context(format!("{ids_string} `{filename}`下载字幕失败"))?; .wrap_err(format!("{ids_string} `{filename}`下载字幕失败"))?;
tracing::debug!("{ids_string} `{filename}`字幕下载任务完成"); tracing::debug!("{ids_string} `{filename}`字幕下载任务完成");
} }
@@ -283,7 +283,7 @@ impl DownloadProgress {
cover_task cover_task
.process(download_task, self) .process(download_task, self)
.await .await
.context(format!("{ids_string} `{filename}`下载封面失败"))?; .wrap_err(format!("{ids_string} `{filename}`下载封面失败"))?;
tracing::debug!("{ids_string} `{filename}`封面下载任务完成"); tracing::debug!("{ids_string} `{filename}`封面下载任务完成");
} }
@@ -291,7 +291,7 @@ impl DownloadProgress {
nfo_task nfo_task
.process(download_task, self, &mut episode_info) .process(download_task, self, &mut episode_info)
.await .await
.context(format!("{ids_string} `{filename}`下载NFO失败"))?; .wrap_err(format!("{ids_string} `{filename}`下载NFO失败"))?;
tracing::debug!("{ids_string} `{filename}`NFO下载任务完成"); tracing::debug!("{ids_string} `{filename}`NFO下载任务完成");
} }
@@ -299,7 +299,7 @@ impl DownloadProgress {
json_task json_task
.process(download_task, self, &mut episode_info) .process(download_task, self, &mut episode_info)
.await .await
.context(format!("{ids_string} `{filename}`下载JSON元数据失败"))?; .wrap_err(format!("{ids_string} `{filename}`下载JSON元数据失败"))?;
tracing::debug!("{ids_string} `{filename}`JSON元数据下载任务完成"); tracing::debug!("{ids_string} `{filename}`JSON元数据下载任务完成");
} }
@@ -314,7 +314,7 @@ impl DownloadProgress {
Ok(()) Ok(())
} }
async fn prepare(&mut self, app: &AppHandle) -> anyhow::Result<()> { async fn prepare(&mut self, app: &AppHandle) -> eyre::Result<()> {
let video_selected = self.video_task.selected; let video_selected = self.video_task.selected;
let video_completed = self.video_task.completed; let video_completed = self.video_task.completed;
let audio_selected = self.audio_task.selected; let audio_selected = self.audio_task.selected;
@@ -323,7 +323,7 @@ impl DownloadProgress {
if (!video_selected && !audio_selected) || (video_completed && audio_completed) { if (!video_selected && !audio_selected) || (video_completed && audio_completed) {
// 如果视频和音频都没有选中,或者都已经完成,则更新需要格式化的字段就返回 // 如果视频和音频都没有选中,或者都已经完成,则更新需要格式化的字段就返回
self.update_fmt_fields(app) self.update_fmt_fields(app)
.context("更新需要格式化的字段失败")?; .wrap_err("更新需要格式化的字段失败")?;
return Ok(()); return Ok(());
} }
@@ -332,12 +332,12 @@ impl DownloadProgress {
match self.episode_type { match self.episode_type {
EpisodeType::Normal => { EpisodeType::Normal => {
let Some(bvid) = &self.bvid else { let Some(bvid) = &self.bvid else {
return Err(anyhow!("progress中的bvid为None,无法获取视频链接")); return Err(eyre!("progress中的bvid为None,无法获取视频链接"));
}; };
let media_url = bili_client let media_url = bili_client
.get_normal_url(bvid, self.cid) .get_normal_url(bvid, self.cid)
.await .await
.context("获取视频链接失败")?; .wrap_err("获取视频链接失败")?;
self.is_preview = !media_url.durl.is_empty() && media_url.dash.video.is_empty(); self.is_preview = !media_url.durl.is_empty() && media_url.dash.video.is_empty();
@@ -355,7 +355,7 @@ impl DownloadProgress {
let media_url = bili_client let media_url = bili_client
.get_bangumi_url(self.cid) .get_bangumi_url(self.cid)
.await .await
.context("获取番剧视频链接失败")?; .wrap_err("获取番剧视频链接失败")?;
self.is_preview = media_url.is_preview != 0; self.is_preview = media_url.is_preview != 0;
@@ -371,12 +371,12 @@ impl DownloadProgress {
} }
EpisodeType::Cheese => { EpisodeType::Cheese => {
let Some(ep_id) = self.ep_id else { let Some(ep_id) = self.ep_id else {
return Err(anyhow!("progress中的ep_id为None,无法获取课程视频链接")); return Err(eyre!("progress中的ep_id为None,无法获取课程视频链接"));
}; };
let media_url = bili_client let media_url = bili_client
.get_cheese_url(ep_id) .get_cheese_url(ep_id)
.await .await
.context("获取课程视频链接失败")?; .wrap_err("获取课程视频链接失败")?;
self.is_drm = media_url.is_drm; self.is_drm = media_url.is_drm;
self.is_preview = media_url.is_preview != 0; self.is_preview = media_url.is_preview != 0;
@@ -394,12 +394,12 @@ impl DownloadProgress {
} }
self.update_fmt_fields(app) self.update_fmt_fields(app)
.context("更新需要格式化的字段失败")?; .wrap_err("更新需要格式化的字段失败")?;
Ok(()) Ok(())
} }
fn update_fmt_fields(&mut self, app: &AppHandle) -> anyhow::Result<()> { fn update_fmt_fields(&mut self, app: &AppHandle) -> eyre::Result<()> {
let fmt_params = self.create_fmt_params(); let fmt_params = self.create_fmt_params();
let config = app.get_config().read().clone(); let config = app.get_config().read().clone();
@@ -435,7 +435,7 @@ impl DownloadProgress {
} }
} }
pub fn save(&self, app: &AppHandle, allow_create: bool) -> anyhow::Result<()> { pub fn save(&self, app: &AppHandle, allow_create: bool) -> eyre::Result<()> {
let progress = self.clone(); let progress = self.clone();
let file_name = format!("{}.json", progress.task_id); let file_name = format!("{}.json", progress.task_id);
@@ -490,7 +490,7 @@ fn create_normal_progresses_for_single(
info: &NormalInfo, info: &NormalInfo,
cid: Option<i64>, cid: Option<i64>,
config: &Config, config: &Config,
) -> anyhow::Result<Vec<DownloadProgress>> { ) -> eyre::Result<Vec<DownloadProgress>> {
let tasks = Tasks::new(config, &info.pic); let tasks = Tasks::new(config, &info.pic);
let create_ts = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); let create_ts = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
@@ -498,7 +498,7 @@ fn create_normal_progresses_for_single(
if let Some(cid) = cid { if let Some(cid) = cid {
// 如果有cid,则说明是要下载单个分P // 如果有cid,则说明是要下载单个分P
let Some(page) = info.pages.iter().find(|p| p.cid == cid) else { let Some(page) = info.pages.iter().find(|p| p.cid == cid) else {
return Err(anyhow!("找不到cid为`{cid}`的分P")); return Err(eyre!("找不到cid为`{cid}`的分P"));
}; };
let progress = DownloadProgress { let progress = DownloadProgress {
task_id: Uuid::new_v4().to_string(), task_id: Uuid::new_v4().to_string(),
@@ -621,12 +621,12 @@ fn create_normal_progresses_for_season(
aid: i64, aid: i64,
cid: Option<i64>, cid: Option<i64>,
config: &Config, config: &Config,
) -> anyhow::Result<Vec<DownloadProgress>> { ) -> eyre::Result<Vec<DownloadProgress>> {
let section_index = ugc_season let section_index = ugc_season
.sections .sections
.iter() .iter()
.position(|s| s.episodes.iter().any(|e| e.aid == aid)) .position(|s| s.episodes.iter().any(|e| e.aid == aid))
.context(format!("找不到含有aid为`{aid}`的ep的section"))?; .ok_or_eyre(format!("找不到含有aid为`{aid}`的ep的section"))?;
let section = &ugc_season.sections[section_index]; let section = &ugc_season.sections[section_index];
#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_possible_wrap)]
let (ep, episode_order) = section let (ep, episode_order) = section
@@ -635,7 +635,7 @@ fn create_normal_progresses_for_season(
.enumerate() .enumerate()
.map(|(i, e)| (e, i as i64 + 1)) .map(|(i, e)| (e, i as i64 + 1))
.find(|(e, _)| e.aid == aid) .find(|(e, _)| e.aid == aid)
.context(format!("在section中找不到aid为`{aid}`的ep"))?; .ok_or_eyre(format!("在section中找不到aid为`{aid}`的ep"))?;
let tasks = Tasks::new(config, &ep.arc.pic); let tasks = Tasks::new(config, &ep.arc.pic);
@@ -644,7 +644,7 @@ fn create_normal_progresses_for_season(
if let Some(cid) = cid { if let Some(cid) = cid {
// 如果有cid,则说明是要下载单个分P // 如果有cid,则说明是要下载单个分P
let Some(page) = ep.pages.iter().find(|p| p.cid == cid) else { let Some(page) = ep.pages.iter().find(|p| p.cid == cid) else {
return Err(anyhow!("找不到cid为`{cid}`的分P")); return Err(eyre!("找不到cid为`{cid}`的分P"));
}; };
let progress = DownloadProgress { let progress = DownloadProgress {
task_id: Uuid::new_v4().to_string(), task_id: Uuid::new_v4().to_string(),
+6 -6
View File
@@ -1,6 +1,6 @@
use std::{sync::Arc, time::Duration}; use std::{sync::Arc, time::Duration};
use anyhow::Context; use eyre::WrapErr;
use parking_lot::RwLock; use parking_lot::RwLock;
use tauri::AppHandle; use tauri::AppHandle;
use tauri_specta::Event; use tauri_specta::Event;
@@ -11,7 +11,7 @@ use tokio::{
use crate::{ use crate::{
events::DownloadEvent, events::DownloadEvent,
extensions::{AnyhowErrorToStringChain, AppHandleExt}, extensions::{AppHandleExt, EyreToStringChain},
types::create_download_task_params::CreateDownloadTaskParams, types::create_download_task_params::CreateDownloadTaskParams,
}; };
@@ -234,7 +234,7 @@ impl DownloadTask {
if let Err(err) = progress if let Err(err) = progress
.process(self) .process(self)
.await .await
.context("[继续]失败的任务可以断点续传") .wrap_err("[继续]失败的任务可以断点续传")
{ {
let err_title = format!("{ids_string} `{episode_title}`下载失败"); let err_title = format!("{ids_string} `{episode_title}`下载失败");
let string_chain = err.to_string_chain(); let string_chain = err.to_string_chain();
@@ -283,7 +283,7 @@ impl DownloadTask {
.task_sem .task_sem
.acquire() .acquire()
.await .await
.map_err(anyhow::Error::from) .map_err(eyre::Report::from)
{ {
Ok(permit) => Some(permit), Ok(permit) => Some(permit),
Err(err) => { Err(err) => {
@@ -306,7 +306,7 @@ impl DownloadTask {
if let Err(err) = self if let Err(err) = self
.state_sender .state_sender
.send(DownloadTaskState::Downloading) .send(DownloadTaskState::Downloading)
.map_err(anyhow::Error::from) .map_err(eyre::Report::from)
{ {
let err_title = format!("{ids_string} `{episode_title}`发送状态`Downloading`失败"); let err_title = format!("{ids_string} `{episode_title}`发送状态`Downloading`失败");
let string_chain = err.to_string_chain(); let string_chain = err.to_string_chain();
@@ -350,7 +350,7 @@ impl DownloadTask {
(progress.episode_title.clone(), progress.get_ids_string()) (progress.episode_title.clone(), progress.get_ids_string())
}; };
if let Err(err) = self.state_sender.send(state).map_err(anyhow::Error::from) { 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!("{ids_string} `{episode_title}`发送状态`{state:?}`失败");
let string_chain = err.to_string_chain(); let string_chain = err.to_string_chain();
tracing::error!(err_title, message = string_chain); tracing::error!(err_title, message = string_chain);
+8 -8
View File
@@ -1,4 +1,4 @@
use anyhow::Context; use eyre::{OptionExt, WrapErr};
use tauri::AppHandle; use tauri::AppHandle;
use crate::{ use crate::{
@@ -23,7 +23,7 @@ pub trait GetOrInitEpisodeInfo {
&'a mut self, &'a mut self,
app: &AppHandle, app: &AppHandle,
progress: &DownloadProgress, progress: &DownloadProgress,
) -> anyhow::Result<&'a mut EpisodeInfo>; ) -> eyre::Result<&'a mut EpisodeInfo>;
} }
impl GetOrInitEpisodeInfo for Option<EpisodeInfo> { impl GetOrInitEpisodeInfo for Option<EpisodeInfo> {
@@ -31,7 +31,7 @@ impl GetOrInitEpisodeInfo for Option<EpisodeInfo> {
&'a mut self, &'a mut self,
app: &AppHandle, app: &AppHandle,
progress: &DownloadProgress, progress: &DownloadProgress,
) -> anyhow::Result<&'a mut EpisodeInfo> { ) -> eyre::Result<&'a mut EpisodeInfo> {
if let Some(info) = self { if let Some(info) = self {
return Ok(info); return Ok(info);
} }
@@ -44,23 +44,23 @@ impl GetOrInitEpisodeInfo for Option<EpisodeInfo> {
let info = bili_client let info = bili_client
.get_normal_info(GetNormalInfoParams::Aid(aid)) .get_normal_info(GetNormalInfoParams::Aid(aid))
.await .await
.context("获取普通视频信息失败")?; .wrap_err("获取普通视频信息失败")?;
EpisodeInfo::Normal(info) EpisodeInfo::Normal(info)
} }
EpisodeType::Bangumi => { EpisodeType::Bangumi => {
let ep_id = ep_id.context("ep_id为None")?; let ep_id = ep_id.ok_or_eyre("ep_id为None")?;
let info = bili_client let info = bili_client
.get_bangumi_info(GetBangumiInfoParams::EpId(ep_id)) .get_bangumi_info(GetBangumiInfoParams::EpId(ep_id))
.await .await
.context("获取番剧信息失败")?; .wrap_err("获取番剧信息失败")?;
EpisodeInfo::Bangumi(info, ep_id) EpisodeInfo::Bangumi(info, ep_id)
} }
EpisodeType::Cheese => { EpisodeType::Cheese => {
let ep_id = ep_id.context("ep_id为None")?; let ep_id = ep_id.ok_or_eyre("ep_id为None")?;
let info = bili_client let info = bili_client
.get_cheese_info(GetCheeseInfoParams::EpId(ep_id)) .get_cheese_info(GetCheeseInfoParams::EpId(ep_id))
.await .await
.context("获取课程信息失败")?; .wrap_err("获取课程信息失败")?;
EpisodeInfo::Cheese(info, ep_id) EpisodeInfo::Cheese(info, ep_id)
} }
}; };
+6 -9
View File
@@ -1,6 +1,6 @@
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf};
use anyhow::Context; use eyre::{OptionExt, WrapErr};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{Map, Value}; use serde_json::{Map, Value};
@@ -36,18 +36,15 @@ pub struct FmtParams {
} }
impl FmtParams { impl FmtParams {
pub fn get_episode_dir_and_filename( pub fn get_episode_dir_and_filename(&self, config: &Config) -> eyre::Result<(PathBuf, String)> {
&self,
config: &Config,
) -> anyhow::Result<(PathBuf, String)> {
use strfmt::strfmt; use strfmt::strfmt;
let mut json_value = let mut json_value =
serde_json::to_value(self).context("将FmtParams转为serde_json::Value失败")?; serde_json::to_value(self).wrap_err("将FmtParams转为serde_json::Value失败")?;
let json_map = json_value let json_map = json_value
.as_object_mut() .as_object_mut()
.context("FmtParams不是JSON对象")?; .ok_or_eyre("FmtParams不是JSON对象")?;
// 格式化时间字段 // 格式化时间字段
format_time_fields(json_map, &config.time_fmt); format_time_fields(json_map, &config.time_fmt);
@@ -74,7 +71,7 @@ impl FmtParams {
let dir_fmt_parts: Vec<&str> = dir_fmt.split('/').collect(); let dir_fmt_parts: Vec<&str> = dir_fmt.split('/').collect();
let mut dir_names = Vec::new(); let mut dir_names = Vec::new();
for fmt in dir_fmt_parts { for fmt in dir_fmt_parts {
let dir_name = strfmt(fmt, &vars).context("格式化目录名失败")?; let dir_name = strfmt(fmt, &vars).wrap_err("格式化目录名失败")?;
let dir_name = filename_filter(&dir_name); let dir_name = filename_filter(&dir_name);
if !dir_name.is_empty() { if !dir_name.is_empty() {
dir_names.push(dir_name); dir_names.push(dir_name);
@@ -82,7 +79,7 @@ impl FmtParams {
} }
// 最后一部分是文件名 // 最后一部分是文件名
let filename = dir_names.pop().context("没有找到文件名部分")?; let filename = dir_names.pop().ok_or_eyre("没有找到文件名部分")?;
// 剩下的部分是目录名 // 剩下的部分是目录名
let mut episode_dir = config.download_dir.clone(); let mut episode_dir = config.download_dir.clone();
for dir_name in dir_names { for dir_name in dir_names {
+12 -12
View File
@@ -4,7 +4,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use anyhow::{Context, anyhow}; use eyre::{WrapErr, eyre};
use fs4::fs_std::FileExt; use fs4::fs_std::FileExt;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -18,7 +18,7 @@ use crate::{
download_chunk_task::DownloadChunkTask, download_progress::DownloadProgress, download_chunk_task::DownloadChunkTask, download_progress::DownloadProgress,
download_task::DownloadTask, media_chunk::MediaChunk, download_task::DownloadTask, media_chunk::MediaChunk,
}, },
extensions::{AnyhowErrorToStringChain, AppHandleExt}, extensions::{AppHandleExt, EyreToStringChain},
types::{ types::{
audio_quality::AudioQuality, bangumi_media_url::BangumiMediaUrl, audio_quality::AudioQuality, bangumi_media_url::BangumiMediaUrl,
cheese_media_url::CheeseMediaUrl, normal_media_url::NormalMediaUrl, cheese_media_url::CheeseMediaUrl, normal_media_url::NormalMediaUrl,
@@ -45,7 +45,7 @@ impl AudioTask {
&mut self, &mut self,
app: &AppHandle, app: &AppHandle,
media_url: &NormalMediaUrl, media_url: &NormalMediaUrl,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let mut join_set = JoinSet::new(); let mut join_set = JoinSet::new();
if let Some(medias) = &media_url.dash.audio { if let Some(medias) = &media_url.dash.audio {
@@ -130,7 +130,7 @@ impl AudioTask {
&mut self, &mut self,
app: &AppHandle, app: &AppHandle,
media_url: &BangumiMediaUrl, media_url: &BangumiMediaUrl,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let Some(dash) = &media_url.dash else { let Some(dash) = &media_url.dash else {
// 如果没有音频,则直接返回 // 如果没有音频,则直接返回
self.completed = true; self.completed = true;
@@ -190,7 +190,7 @@ impl AudioTask {
&mut self, &mut self,
app: &AppHandle, app: &AppHandle,
media_url: &CheeseMediaUrl, media_url: &CheeseMediaUrl,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let Some(dash) = &media_url.dash else { let Some(dash) = &media_url.dash else {
// 如果没有音频,则直接返回 // 如果没有音频,则直接返回
self.completed = true; self.completed = true;
@@ -314,7 +314,7 @@ impl AudioTask {
&self, &self,
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let (audio_task, episode_title, ids_string) = { let (audio_task, episode_title, ids_string) = {
( (
@@ -378,7 +378,7 @@ impl AudioTask {
}; };
join_set.spawn(async move { join_set.spawn(async move {
download_chunk_task.process().await.context(format!( download_chunk_task.process().await.wrap_err(format!(
"分片`{chunk_index}/{chunk_count}`下载失败({start}-{end})" "分片`{chunk_index}/{chunk_count}`下载失败({start}-{end})"
)) ))
}); });
@@ -407,20 +407,20 @@ impl AudioTask {
.iter() .iter()
.all(|chunk| chunk.completed); .all(|chunk| chunk.completed);
if !download_completed { if !download_completed {
return Err(anyhow!( return Err(eyre!(
"音频文件`{}`有分片未下载完成,[继续]可以跳过已下载分片断点续传", "音频文件`{}`有分片未下载完成,[继续]可以跳过已下载分片断点续传",
temp_file_path.display() temp_file_path.display()
)); ));
} }
let is_audio_file_complete = utils::is_mp4_complete(&temp_file_path).context(format!( let is_audio_file_complete = utils::is_mp4_complete(&temp_file_path).wrap_err(format!(
"检查音频文件`{}`是否完整失败", "检查音频文件`{}`是否完整失败",
temp_file_path.display() temp_file_path.display()
))?; ))?;
if !is_audio_file_complete { if !is_audio_file_complete {
download_task.update_progress(|p| p.audio_task.mark_uncompleted()); download_task.update_progress(|p| p.audio_task.mark_uncompleted());
return Err(anyhow!( return Err(eyre!(
"音频文件`{}`不完整,[继续]会重新下载所有分片", "音频文件`{}`不完整,[继续]会重新下载所有分片",
temp_file_path.display() temp_file_path.display()
)); ));
@@ -429,9 +429,9 @@ impl AudioTask {
// 重命名临时文件 // 重命名临时文件
if m4a_path.exists() { if m4a_path.exists() {
std::fs::remove_file(&m4a_path) std::fs::remove_file(&m4a_path)
.context(format!("删除已存在的音频文件`{}`失败", m4a_path.display()))?; .wrap_err(format!("删除已存在的音频文件`{}`失败", m4a_path.display()))?;
} }
std::fs::rename(&temp_file_path, &m4a_path).context(format!( std::fs::rename(&temp_file_path, &m4a_path).wrap_err(format!(
"将临时文件`{}`重命名为`{}`失败", "将临时文件`{}`重命名为`{}`失败",
temp_file_path.display(), temp_file_path.display(),
m4a_path.display() m4a_path.display()
+4 -4
View File
@@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context; use eyre::WrapErr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
@@ -30,18 +30,18 @@ impl CoverTask {
&self, &self,
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let bili_client = download_task.app.get_bili_client(); let bili_client = download_task.app.get_bili_client();
let (cover_data, ext) = bili_client let (cover_data, ext) = bili_client
.get_cover_data_and_ext(&progress.cover_task.url) .get_cover_data_and_ext(&progress.cover_task.url)
.await .await
.context("获取封面失败")?; .wrap_err("获取封面失败")?;
let save_path = episode_dir.join(format!("{filename}.{ext}")); let save_path = episode_dir.join(format!("{filename}.{ext}"));
std::fs::write(&save_path, cover_data) std::fs::write(&save_path, cover_data)
.context(format!("保存封面到`{}`失败", save_path.display()))?; .wrap_err(format!("保存封面到`{}`失败", save_path.display()))?;
download_task.update_progress(|p| p.cover_task.completed = true); download_task.update_progress(|p| p.cover_task.completed = true);
@@ -1,6 +1,6 @@
use std::{fs::File, sync::Arc}; use std::{fs::File, sync::Arc};
use anyhow::Context; use eyre::WrapErr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
@@ -37,7 +37,7 @@ impl DanmakuTask {
&self, &self,
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let danmaku_task = &progress.danmaku_task; let danmaku_task = &progress.danmaku_task;
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string(); let ids_string = progress.get_ids_string();
@@ -66,29 +66,29 @@ impl DanmakuTask {
let replies = bili_client let replies = bili_client
.get_danmaku(progress.aid, progress.cid, progress.duration) .get_danmaku(progress.aid, progress.cid, progress.duration)
.await .await
.context("获取弹幕失败")?; .wrap_err("获取弹幕失败")?;
let xml = replies let xml = replies
.to_xml(progress.cid) .to_xml(progress.cid)
.context("将弹幕转换为XML失败")?; .wrap_err("将弹幕转换为XML失败")?;
if danmaku_task.xml_selected { if danmaku_task.xml_selected {
std::fs::write(&xml_path, &xml) std::fs::write(&xml_path, &xml)
.context(format!("保存弹幕XML到`{}`失败", xml_path.display()))?; .wrap_err(format!("保存弹幕XML到`{}`失败", xml_path.display()))?;
} }
if danmaku_task.ass_selected { if danmaku_task.ass_selected {
let config = download_task.app.get_config().read().danmaku_config.clone(); let config = download_task.app.get_config().read().danmaku_config.clone();
let ass_file = File::create(&ass_path) let ass_file = File::create(&ass_path)
.context(format!("创建弹幕ASS文件`{}`失败", ass_path.display()))?; .wrap_err(format!("创建弹幕ASS文件`{}`失败", ass_path.display()))?;
let title = filename.clone(); let title = filename.clone();
xml_to_ass(&xml, ass_file, title, config).context("将弹幕XML转换为ASS失败")?; xml_to_ass(&xml, ass_file, title, config).wrap_err("将弹幕XML转换为ASS失败")?;
} }
if danmaku_task.json_selected { if danmaku_task.json_selected {
let json_string = serde_json::to_string(&replies).context("将弹幕转换为JSON失败")?; let json_string = serde_json::to_string(&replies).wrap_err("将弹幕转换为JSON失败")?;
std::fs::write(&json_path, json_string) std::fs::write(&json_path, json_string)
.context(format!("保存弹幕JSON到`{}`失败", json_path.display()))?; .wrap_err(format!("保存弹幕JSON到`{}`失败", json_path.display()))?;
} }
download_task.update_progress(|p| p.danmaku_task.completed = true); download_task.update_progress(|p| p.danmaku_task.completed = true);
+6 -6
View File
@@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context; use eyre::WrapErr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
@@ -31,7 +31,7 @@ impl JsonTask {
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
episode_info: &mut Option<EpisodeInfo>, episode_info: &mut Option<EpisodeInfo>,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let episode_info = episode_info let episode_info = episode_info
@@ -41,17 +41,17 @@ impl JsonTask {
let json_path = episode_dir.join(format!("{filename}-元数据.json")); let json_path = episode_dir.join(format!("{filename}-元数据.json"));
let json_string = match episode_info { let json_string = match episode_info {
EpisodeInfo::Normal(info) => { EpisodeInfo::Normal(info) => {
serde_json::to_string(&info).context("将普通视频信息转换为JSON失败")? serde_json::to_string(&info).wrap_err("将普通视频信息转换为JSON失败")?
} }
EpisodeInfo::Bangumi(info, _ep_id) => { EpisodeInfo::Bangumi(info, _ep_id) => {
serde_json::to_string(&info).context("将番剧信息转换为JSON失败")? serde_json::to_string(&info).wrap_err("将番剧信息转换为JSON失败")?
} }
EpisodeInfo::Cheese(info, _ep_id) => { EpisodeInfo::Cheese(info, _ep_id) => {
serde_json::to_string(&info).context("将课程信息转换为JSON失败")? serde_json::to_string(&info).wrap_err("将课程信息转换为JSON失败")?
} }
}; };
std::fs::write(&json_path, json_string) std::fs::write(&json_path, json_string)
.context(format!("保存JSON到`{}`失败", json_path.display()))?; .wrap_err(format!("保存JSON到`{}`失败", json_path.display()))?;
download_task.update_progress(|p| p.json_task.completed = true); download_task.update_progress(|p| p.json_task.completed = true);
+44 -44
View File
@@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::{Context, anyhow};
use chrono::{DateTime, Datelike, NaiveDateTime}; use chrono::{DateTime, Datelike, NaiveDateTime};
use eyre::{OptionExt, WrapErr, eyre};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
use yaserde::{YaDeserialize, YaSerialize}; use yaserde::{YaDeserialize, YaSerialize};
@@ -42,7 +42,7 @@ impl NfoTask {
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
episode_info: &mut Option<EpisodeInfo>, episode_info: &mut Option<EpisodeInfo>,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let episode_info = episode_info let episode_info = episode_info
.get_or_init(&download_task.app, progress) .get_or_init(&download_task.app, progress)
.await?; .await?;
@@ -69,7 +69,7 @@ impl NfoTask {
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
info: &NormalInfo, info: &NormalInfo,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string(); let ids_string = progress.get_ids_string();
let nfo_path = episode_dir.join(format!("{filename}.nfo")); let nfo_path = episode_dir.join(format!("{filename}.nfo"));
@@ -89,21 +89,21 @@ impl NfoTask {
let tags = bili_client let tags = bili_client
.get_tags(progress.aid) .get_tags(progress.aid)
.await .await
.context("获取视频标签失败")?; .wrap_err("获取视频标签失败")?;
let movie_nfo = info let movie_nfo = info
.to_movie_nfo(tags) .to_movie_nfo(tags)
.context("将普通视频信息转换为movie NFO失败")?; .wrap_err("将普通视频信息转换为movie NFO失败")?;
std::fs::write(&nfo_path, movie_nfo) std::fs::write(&nfo_path, movie_nfo)
.context(format!("保存普通视频NFO到`{}`失败", nfo_path.display()))?; .wrap_err(format!("保存普通视频NFO到`{}`失败", nfo_path.display()))?;
if let Some(ugc_season) = &info.ugc_season { if let Some(ugc_season) = &info.ugc_season {
let collection_cover = &ugc_season.cover; let collection_cover = &ugc_season.cover;
let (cover_data, ext) = bili_client let (cover_data, ext) = bili_client
.get_cover_data_and_ext(collection_cover) .get_cover_data_and_ext(collection_cover)
.await .await
.context("获取普通视频合集封面失败")?; .wrap_err("获取普通视频合集封面失败")?;
let cover_path = episode_dir.join(format!("poster.{ext}")); let cover_path = episode_dir.join(format!("poster.{ext}"));
std::fs::write(&cover_path, cover_data).context(format!( std::fs::write(&cover_path, cover_data).wrap_err(format!(
"保存普通视频合集封面到`{}`失败", "保存普通视频合集封面到`{}`失败",
cover_path.display() cover_path.display()
))?; ))?;
@@ -120,7 +120,7 @@ impl NfoTask {
progress: &DownloadProgress, progress: &DownloadProgress,
info: &BangumiInfo, info: &BangumiInfo,
ep_id: &i64, ep_id: &i64,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string(); let ids_string = progress.get_ids_string();
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo")); let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
@@ -139,16 +139,16 @@ impl NfoTask {
let tvshow_nfo = info let tvshow_nfo = info
.to_tvshow_nfo() .to_tvshow_nfo()
.context("将番剧信息转换为tvshow NFO失败")?; .wrap_err("将番剧信息转换为tvshow NFO失败")?;
let tvshow_nfo_path = episode_dir.join("tvshow.nfo"); let tvshow_nfo_path = episode_dir.join("tvshow.nfo");
std::fs::write(&tvshow_nfo_path, tvshow_nfo) std::fs::write(&tvshow_nfo_path, tvshow_nfo)
.context(format!("保存番剧NFO到`{}`失败", tvshow_nfo_path.display()))?; .wrap_err(format!("保存番剧NFO到`{}`失败", tvshow_nfo_path.display()))?;
let episode_details_nfo = info let episode_details_nfo = info
.to_episode_details_nfo(*ep_id) .to_episode_details_nfo(*ep_id)
.context("将番剧信息转换为episodedetail NFO失败")?; .wrap_err("将番剧信息转换为episodedetail NFO失败")?;
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo")); let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
std::fs::write(&episode_details_nfo_path, episode_details_nfo).context(format!( std::fs::write(&episode_details_nfo_path, episode_details_nfo).wrap_err(format!(
"保存番剧NFO到`{}`失败", "保存番剧NFO到`{}`失败",
episode_details_nfo_path.display() episode_details_nfo_path.display()
))?; ))?;
@@ -157,20 +157,20 @@ impl NfoTask {
let (poster_data, ext) = bili_client let (poster_data, ext) = bili_client
.get_cover_data_and_ext(poster_url) .get_cover_data_and_ext(poster_url)
.await .await
.context("获取番剧封面失败")?; .wrap_err("获取番剧封面失败")?;
let poster_path = episode_dir.join(format!("poster.{ext}")); let poster_path = episode_dir.join(format!("poster.{ext}"));
std::fs::write(&poster_path, poster_data) std::fs::write(&poster_path, poster_data)
.context(format!("保存番剧封面到`{}`失败", poster_path.display()))?; .wrap_err(format!("保存番剧封面到`{}`失败", poster_path.display()))?;
let fanart_url = &info.bkg_cover; let fanart_url = &info.bkg_cover;
if !fanart_url.is_empty() { if !fanart_url.is_empty() {
let (fanart_data, ext) = bili_client let (fanart_data, ext) = bili_client
.get_cover_data_and_ext(fanart_url) .get_cover_data_and_ext(fanart_url)
.await .await
.context("获取番剧封面失败")?; .wrap_err("获取番剧封面失败")?;
let fanart_path = episode_dir.join(format!("fanart.{ext}")); let fanart_path = episode_dir.join(format!("fanart.{ext}"));
std::fs::write(&fanart_path, fanart_data) std::fs::write(&fanart_path, fanart_data)
.context(format!("保存番剧封面到`{}`失败", fanart_path.display()))?; .wrap_err(format!("保存番剧封面到`{}`失败", fanart_path.display()))?;
} }
download_task.update_progress(|p| p.nfo_task.completed = true); download_task.update_progress(|p| p.nfo_task.completed = true);
@@ -184,7 +184,7 @@ impl NfoTask {
progress: &DownloadProgress, progress: &DownloadProgress,
info: &CheeseInfo, info: &CheeseInfo,
ep_id: &i64, ep_id: &i64,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ids_string = progress.get_ids_string(); let ids_string = progress.get_ids_string();
let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo")); let episode_details_nfo_path = episode_dir.join(format!("{filename}.nfo"));
@@ -203,15 +203,15 @@ impl NfoTask {
let tvshow_nfo = info let tvshow_nfo = info
.to_tvshow_nfo() .to_tvshow_nfo()
.context("将课程信息转换为tvshow NFO失败")?; .wrap_err("将课程信息转换为tvshow NFO失败")?;
let tvshow_nfo_path = episode_dir.join("tvshow.nfo"); let tvshow_nfo_path = episode_dir.join("tvshow.nfo");
std::fs::write(&tvshow_nfo_path, tvshow_nfo) std::fs::write(&tvshow_nfo_path, tvshow_nfo)
.context(format!("保存课程NFO到`{}`失败", tvshow_nfo_path.display()))?; .wrap_err(format!("保存课程NFO到`{}`失败", tvshow_nfo_path.display()))?;
let episode_details_nfo = info let episode_details_nfo = info
.to_episode_details_nfo(*ep_id) .to_episode_details_nfo(*ep_id)
.context("将课程信息转换为episodedetail NFO失败")?; .wrap_err("将课程信息转换为episodedetail NFO失败")?;
std::fs::write(&episode_details_nfo_path, episode_details_nfo).context(format!( std::fs::write(&episode_details_nfo_path, episode_details_nfo).wrap_err(format!(
"保存课程NFO到`{}`失败", "保存课程NFO到`{}`失败",
episode_details_nfo_path.display() episode_details_nfo_path.display()
))?; ))?;
@@ -220,10 +220,10 @@ impl NfoTask {
let (poster_data, ext) = bili_client let (poster_data, ext) = bili_client
.get_cover_data_and_ext(poster_url) .get_cover_data_and_ext(poster_url)
.await .await
.context("获取课程封面失败")?; .wrap_err("获取课程封面失败")?;
let poster_path = episode_dir.join(format!("poster.{ext}")); let poster_path = episode_dir.join(format!("poster.{ext}"));
std::fs::write(&poster_path, poster_data) std::fs::write(&poster_path, poster_data)
.context(format!("保存课程封面到`{}`失败", poster_path.display()))?; .wrap_err(format!("保存课程封面到`{}`失败", poster_path.display()))?;
download_task.update_progress(|p| p.nfo_task.completed = true); download_task.update_progress(|p| p.nfo_task.completed = true);
@@ -299,7 +299,7 @@ struct EpisodeDetails {
} }
impl NormalInfo { impl NormalInfo {
pub fn to_movie_nfo(&self, tags: Tags) -> anyhow::Result<String> { pub fn to_movie_nfo(&self, tags: Tags) -> eyre::Result<String> {
let genre = vec![ let genre = vec![
"Bilibili视频".to_string(), "Bilibili视频".to_string(),
self.tname.clone(), self.tname.clone(),
@@ -314,7 +314,7 @@ impl NormalInfo {
let ts = self.pubdate; let ts = self.pubdate;
let date_time = DateTime::from_timestamp(ts, 0) let date_time = DateTime::from_timestamp(ts, 0)
.context(format!("将视频发布时间戳转换为日期时间失败: {ts}"))? .ok_or_eyre(format!("将视频发布时间戳转换为日期时间失败: {ts}"))?
.with_timezone(&chrono::Local); .with_timezone(&chrono::Local);
let set = self.ugc_season.as_ref().map(|ugc_season| Set { let set = self.ugc_season.as_ref().map(|ugc_season| Set {
@@ -357,16 +357,16 @@ impl NormalInfo {
..Default::default() ..Default::default()
}; };
let nfo = yaserde::ser::to_string_with_config(&movie, &cfg).map_err(|e| anyhow!(e))?; let nfo = yaserde::ser::to_string_with_config(&movie, &cfg).map_err(|e| eyre!(e))?;
Ok(nfo) Ok(nfo)
} }
} }
impl BangumiInfo { impl BangumiInfo {
pub fn to_tvshow_nfo(&self) -> anyhow::Result<String> { pub fn to_tvshow_nfo(&self) -> eyre::Result<String> {
let time_str = &self.publish.pub_time; let time_str = &self.publish.pub_time;
let date_time = NaiveDateTime::parse_from_str(time_str, "%Y-%m-%d %H:%M:%S").context( let date_time = NaiveDateTime::parse_from_str(time_str, "%Y-%m-%d %H:%M:%S").wrap_err(
format!("将番剧发布时间字符串转换为日期时间失败: {time_str}"), format!("将番剧发布时间字符串转换为日期时间失败: {time_str}"),
)?; )?;
@@ -394,30 +394,30 @@ impl BangumiInfo {
..Default::default() ..Default::default()
}; };
let nfo = yaserde::ser::to_string_with_config(&tv_show, &cfg).map_err(|e| anyhow!(e))?; let nfo = yaserde::ser::to_string_with_config(&tv_show, &cfg).map_err(|e| eyre!(e))?;
Ok(nfo) Ok(nfo)
} }
pub fn to_episode_details_nfo(&self, ep_id: i64) -> anyhow::Result<String> { pub fn to_episode_details_nfo(&self, ep_id: i64) -> eyre::Result<String> {
let (episode, episode_order) = self.get_episode_with_order(ep_id)?; let (episode, episode_order) = self.get_episode_with_order(ep_id)?;
let ts = episode.pub_time; let ts = episode.pub_time;
let date_time = DateTime::from_timestamp(ts, 0) let date_time = DateTime::from_timestamp(ts, 0)
.context(format!("将番剧发布时间戳转换为日期时间失败: {ts}"))? .ok_or_eyre(format!("将番剧发布时间戳转换为日期时间失败: {ts}"))?
.with_timezone(&chrono::Local); .with_timezone(&chrono::Local);
let title = episode let title = episode
.show_title .show_title
.clone() .clone()
.context("episode.show_title为None")?; .ok_or_eyre("episode.show_title为None")?;
let plot = episode let plot = episode
.share_copy .share_copy
.clone() .clone()
.context("episode.share_copy为None")?; .ok_or_eyre("episode.share_copy为None")?;
let duration = episode.duration.context("episode.duration为None")?; let duration = episode.duration.ok_or_eyre("episode.duration为None")?;
let episode_details = EpisodeDetails { let episode_details = EpisodeDetails {
title, title,
@@ -440,7 +440,7 @@ impl BangumiInfo {
}; };
let nfo = let nfo =
yaserde::ser::to_string_with_config(&episode_details, &cfg).map_err(|e| anyhow!(e))?; yaserde::ser::to_string_with_config(&episode_details, &cfg).map_err(|e| eyre!(e))?;
Ok(nfo) Ok(nfo)
} }
@@ -489,11 +489,11 @@ impl BangumiInfo {
} }
impl CheeseInfo { impl CheeseInfo {
pub fn to_tvshow_nfo(&self) -> anyhow::Result<String> { pub fn to_tvshow_nfo(&self) -> eyre::Result<String> {
let episode = self.episodes.first().context("episodes列表为空")?; let episode = self.episodes.first().ok_or_eyre("episodes列表为空")?;
let ts = episode.release_date; let ts = episode.release_date;
let date_time = DateTime::from_timestamp(ts, 0) let date_time = DateTime::from_timestamp(ts, 0)
.context(format!("将课程的发布时间戳转换为日期时间失败: {ts}"))? .ok_or_eyre(format!("将课程的发布时间戳转换为日期时间失败: {ts}"))?
.with_timezone(&chrono::Local); .with_timezone(&chrono::Local);
let status = match self.release_status.as_str() { let status = match self.release_status.as_str() {
@@ -520,21 +520,21 @@ impl CheeseInfo {
..Default::default() ..Default::default()
}; };
let nfo = yaserde::ser::to_string_with_config(&tv_show, &cfg).map_err(|e| anyhow!(e))?; let nfo = yaserde::ser::to_string_with_config(&tv_show, &cfg).map_err(|e| eyre!(e))?;
Ok(nfo) Ok(nfo)
} }
pub fn to_episode_details_nfo(&self, ep_id: i64) -> anyhow::Result<String> { pub fn to_episode_details_nfo(&self, ep_id: i64) -> eyre::Result<String> {
let episode = self let episode = self
.episodes .episodes
.iter() .iter()
.find(|ep| ep.id == ep_id) .find(|ep| ep.id == ep_id)
.context(format!("找不到ep_id为`{ep_id}`的课程"))?; .ok_or_eyre(format!("找不到ep_id为`{ep_id}`的课程"))?;
let ts = episode.release_date; let ts = episode.release_date;
let date_time = DateTime::from_timestamp(ts, 0) let date_time = DateTime::from_timestamp(ts, 0)
.context(format!("将课程发布时间戳转换为日期时间失败: {ts}"))? .ok_or_eyre(format!("将课程发布时间戳转换为日期时间失败: {ts}"))?
.with_timezone(&chrono::Local); .with_timezone(&chrono::Local);
let episode_details = EpisodeDetails { let episode_details = EpisodeDetails {
@@ -558,7 +558,7 @@ impl CheeseInfo {
}; };
let nfo = let nfo =
yaserde::ser::to_string_with_config(&episode_details, &cfg).map_err(|e| anyhow!(e))?; yaserde::ser::to_string_with_config(&episode_details, &cfg).map_err(|e| eyre!(e))?;
Ok(nfo) Ok(nfo)
} }
@@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context; use eyre::WrapErr;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
@@ -32,7 +32,7 @@ impl SubtitleTask {
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
player_info: &mut Option<PlayerInfo>, player_info: &mut Option<PlayerInfo>,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
use std::fmt::Write; use std::fmt::Write;
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
@@ -48,7 +48,7 @@ impl SubtitleTask {
let subtitle = bili_client let subtitle = bili_client
.get_subtitle(&url) .get_subtitle(&url)
.await .await
.context("获取字幕失败")?; .wrap_err("获取字幕失败")?;
let mut srt_content = String::new(); let mut srt_content = String::new();
for (i, b) in subtitle.body.iter().enumerate() { for (i, b) in subtitle.body.iter().enumerate() {
@@ -1,6 +1,6 @@
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use anyhow::{Context, anyhow}; use eyre::{WrapErr, eyre};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
use tauri::AppHandle; use tauri::AppHandle;
@@ -43,21 +43,21 @@ impl VideoProcessTask {
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
player_info: &mut Option<PlayerInfo>, player_info: &mut Option<PlayerInfo>,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let embed_selected = self.embed_chapter_selected || self.embed_skip_selected; let embed_selected = self.embed_chapter_selected || self.embed_skip_selected;
if self.merge_selected && embed_selected { if self.merge_selected && embed_selected {
self.merge_and_embed(download_task, progress, player_info) self.merge_and_embed(download_task, progress, player_info)
.await .await
.context("自动合并+嵌入章节元数据失败")?; .wrap_err("自动合并+嵌入章节元数据失败")?;
} else if self.merge_selected { } else if self.merge_selected {
self.merge(download_task, progress) self.merge(download_task, progress)
.await .await
.context("自动合并失败")?; .wrap_err("自动合并失败")?;
} else if embed_selected { } else if embed_selected {
self.embed(download_task, progress, player_info) self.embed(download_task, progress, player_info)
.await .await
.context("嵌入章节元数据失败")?; .wrap_err("嵌入章节元数据失败")?;
} }
Ok(()) Ok(())
@@ -68,10 +68,10 @@ impl VideoProcessTask {
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
player_info: &mut Option<PlayerInfo>, player_info: &mut Option<PlayerInfo>,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ffmpeg_program = utils::get_ffmpeg_program().context("获取FFmpeg程序路径失败")?; let ffmpeg_program = utils::get_ffmpeg_program().wrap_err("获取FFmpeg程序路径失败")?;
let video_path = episode_dir.join(format!("{filename}.mp4")); let video_path = episode_dir.join(format!("{filename}.mp4"));
if !video_path.exists() { if !video_path.exists() {
@@ -84,14 +84,14 @@ impl VideoProcessTask {
// 如果音频文件不存在,则只嵌入章节元数据 // 如果音频文件不存在,则只嵌入章节元数据
self.embed(download_task, progress, player_info) self.embed(download_task, progress, player_info)
.await .await
.context("嵌入章节元数据失败")?; .wrap_err("嵌入章节元数据失败")?;
return Ok(()); return Ok(());
} }
let metadata_path = self let metadata_path = self
.create_chapter_metadata(&download_task.app, progress, player_info) .create_chapter_metadata(&download_task.app, progress, player_info)
.await .await
.context("创建章节元数据失败")?; .wrap_err("创建章节元数据失败")?;
let output_path = episode_dir.join(format!("{filename}-merged.mp4")); let output_path = episode_dir.join(format!("{filename}-merged.mp4"));
@@ -134,24 +134,24 @@ impl VideoProcessTask {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout); let stdout = String::from_utf8_lossy(&output.stdout);
let err = anyhow!(format!("STDOUT: {stdout}")) let err = eyre!(format!("STDOUT: {stdout}"))
.context(format!("STDERR: {stderr}")) .wrap_err(format!("STDERR: {stderr}"))
.context("原因可能是视频或音频文件损坏,建议[重来]试试"); .wrap_err("原因可能是视频或音频文件损坏,建议[重来]试试");
return Err(err); return Err(err);
} }
std::fs::remove_file(&video_path) std::fs::remove_file(&video_path)
.context(format!("删除视频文件`{}`失败", video_path.display()))?; .wrap_err(format!("删除视频文件`{}`失败", video_path.display()))?;
std::fs::remove_file(&audio_path) std::fs::remove_file(&audio_path)
.context(format!("删除音频文件`{}`失败", audio_path.display()))?; .wrap_err(format!("删除音频文件`{}`失败", audio_path.display()))?;
std::fs::rename(&output_path, &video_path).context(format!( std::fs::rename(&output_path, &video_path).wrap_err(format!(
"将`{}`重命名为`{}`失败", "将`{}`重命名为`{}`失败",
output_path.display(), output_path.display(),
video_path.display() video_path.display()
))?; ))?;
if let Some(metadata_path) = metadata_path { if let Some(metadata_path) = metadata_path {
std::fs::remove_file(&metadata_path).context(format!( std::fs::remove_file(&metadata_path).wrap_err(format!(
"删除章节元数据文件`{}`失败", "删除章节元数据文件`{}`失败",
metadata_path.display() metadata_path.display()
))?; ))?;
@@ -166,7 +166,7 @@ impl VideoProcessTask {
&self, &self,
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let video_path = episode_dir.join(format!("{filename}.mp4")); let video_path = episode_dir.join(format!("{filename}.mp4"));
@@ -183,7 +183,7 @@ impl VideoProcessTask {
let output_path = episode_dir.join(format!("{filename}-merged.mp4")); let output_path = episode_dir.join(format!("{filename}-merged.mp4"));
let ffmpeg_program = utils::get_ffmpeg_program().context("获取FFmpeg程序路径失败")?; let ffmpeg_program = utils::get_ffmpeg_program().wrap_err("获取FFmpeg程序路径失败")?;
let (tx, rx) = tokio::sync::oneshot::channel(); let (tx, rx) = tokio::sync::oneshot::channel();
let video_path_clone = video_path.clone(); let video_path_clone = video_path.clone();
@@ -219,17 +219,17 @@ impl VideoProcessTask {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout); let stdout = String::from_utf8_lossy(&output.stdout);
let err = anyhow!(format!("STDOUT: {stdout}")) let err = eyre!(format!("STDOUT: {stdout}"))
.context(format!("STDERR: {stderr}")) .wrap_err(format!("STDERR: {stderr}"))
.context("原因可能是视频或音频文件损坏,建议[重来]试试"); .wrap_err("原因可能是视频或音频文件损坏,建议[重来]试试");
return Err(err); return Err(err);
} }
std::fs::remove_file(&video_path) std::fs::remove_file(&video_path)
.context(format!("删除视频文件`{}`失败", video_path.display()))?; .wrap_err(format!("删除视频文件`{}`失败", video_path.display()))?;
std::fs::remove_file(&audio_path) std::fs::remove_file(&audio_path)
.context(format!("删除音频文件`{}`失败", audio_path.display()))?; .wrap_err(format!("删除音频文件`{}`失败", audio_path.display()))?;
std::fs::rename(&output_path, &video_path).context(format!( std::fs::rename(&output_path, &video_path).wrap_err(format!(
"将`{}`重命名为`{}`失败", "将`{}`重命名为`{}`失败",
output_path.display(), output_path.display(),
video_path.display() video_path.display()
@@ -245,10 +245,10 @@ impl VideoProcessTask {
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
player_info: &mut Option<PlayerInfo>, player_info: &mut Option<PlayerInfo>,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let ffmpeg_program = utils::get_ffmpeg_program().context("获取FFmpeg程序路径失败")?; let ffmpeg_program = utils::get_ffmpeg_program().wrap_err("获取FFmpeg程序路径失败")?;
let video_path = episode_dir.join(format!("{filename}.mp4")); let video_path = episode_dir.join(format!("{filename}.mp4"));
if !video_path.exists() { if !video_path.exists() {
@@ -261,7 +261,7 @@ impl VideoProcessTask {
let metadata_path = self let metadata_path = self
.create_chapter_metadata(&download_task.app, progress, player_info) .create_chapter_metadata(&download_task.app, progress, player_info)
.await .await
.context("创建章节元数据失败")?; .wrap_err("创建章节元数据失败")?;
let Some(metadata_path) = metadata_path else { let Some(metadata_path) = metadata_path else {
download_task.update_progress(|p| p.video_process_task.completed = true); download_task.update_progress(|p| p.video_process_task.completed = true);
@@ -301,20 +301,20 @@ impl VideoProcessTask {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout); let stdout = String::from_utf8_lossy(&output.stdout);
let err = anyhow!(format!("STDOUT: {stdout}")) let err = eyre!(format!("STDOUT: {stdout}"))
.context(format!("STDERR: {stderr}")) .wrap_err(format!("STDERR: {stderr}"))
.context("原因可能是视频或音频文件损坏,建议[重来]试试"); .wrap_err("原因可能是视频或音频文件损坏,建议[重来]试试");
return Err(err); return Err(err);
} }
std::fs::remove_file(&video_path) std::fs::remove_file(&video_path)
.context(format!("删除视频文件`{}`失败", video_path.display()))?; .wrap_err(format!("删除视频文件`{}`失败", video_path.display()))?;
std::fs::rename(&output_path, &video_path).context(format!( std::fs::rename(&output_path, &video_path).wrap_err(format!(
"将`{}`重命名为`{}`失败", "将`{}`重命名为`{}`失败",
output_path.display(), output_path.display(),
video_path.display() video_path.display()
))?; ))?;
std::fs::remove_file(&metadata_path).context(format!( std::fs::remove_file(&metadata_path).wrap_err(format!(
"删除章节元数据文件`{}`失败", "删除章节元数据文件`{}`失败",
metadata_path.display() metadata_path.display()
))?; ))?;
@@ -329,7 +329,7 @@ impl VideoProcessTask {
app: &AppHandle, app: &AppHandle,
progress: &DownloadProgress, progress: &DownloadProgress,
player_info: &mut Option<PlayerInfo>, player_info: &mut Option<PlayerInfo>,
) -> anyhow::Result<Option<PathBuf>> { ) -> eyre::Result<Option<PathBuf>> {
let mut chapter_segments = ChapterSegments { let mut chapter_segments = ChapterSegments {
segments: Vec::new(), segments: Vec::new(),
}; };
@@ -368,7 +368,7 @@ impl VideoProcessTask {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let metadata_path = episode_dir.join(format!("{filename}.FFMETA.ini")); let metadata_path = episode_dir.join(format!("{filename}.FFMETA.ini"));
std::fs::write(&metadata_path, metadata_content) std::fs::write(&metadata_path, metadata_content)
.context(format!("保存章节元数据到`{}`失败", metadata_path.display()))?; .wrap_err(format!("保存章节元数据到`{}`失败", metadata_path.display()))?;
Ok(Some(metadata_path)) Ok(Some(metadata_path))
} }
+16 -16
View File
@@ -4,7 +4,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use anyhow::{Context, anyhow}; use eyre::{OptionExt, WrapErr, eyre};
use fs4::fs_std::FileExt; use fs4::fs_std::FileExt;
use parking_lot::Mutex; use parking_lot::Mutex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -18,7 +18,7 @@ use crate::{
download_chunk_task::DownloadChunkTask, download_progress::DownloadProgress, download_chunk_task::DownloadChunkTask, download_progress::DownloadProgress,
download_task::DownloadTask, media_chunk::MediaChunk, download_task::DownloadTask, media_chunk::MediaChunk,
}, },
extensions::{AnyhowErrorToStringChain, AppHandleExt}, extensions::{AppHandleExt, EyreToStringChain},
types::{ types::{
bangumi_media_url::BangumiMediaUrl, cheese_media_url::CheeseMediaUrl, bangumi_media_url::BangumiMediaUrl, cheese_media_url::CheeseMediaUrl,
codec_type::CodecType, normal_media_url::NormalMediaUrl, video_quality::VideoQuality, codec_type::CodecType, normal_media_url::NormalMediaUrl, video_quality::VideoQuality,
@@ -46,7 +46,7 @@ impl VideoTask {
&mut self, &mut self,
app: &AppHandle, app: &AppHandle,
media_url: &NormalMediaUrl, media_url: &NormalMediaUrl,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let mut join_set = JoinSet::new(); let mut join_set = JoinSet::new();
for media in &media_url.dash.video { for media in &media_url.dash.video {
@@ -110,7 +110,7 @@ impl VideoTask {
&mut self, &mut self,
app: &AppHandle, app: &AppHandle,
media_url: &BangumiMediaUrl, media_url: &BangumiMediaUrl,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let mut medias: Vec<MediaForPrepare> = Vec::new(); let mut medias: Vec<MediaForPrepare> = Vec::new();
let mut join_set = JoinSet::new(); let mut join_set = JoinSet::new();
@@ -180,7 +180,7 @@ impl VideoTask {
&mut self, &mut self,
app: &AppHandle, app: &AppHandle,
media_url: &CheeseMediaUrl, media_url: &CheeseMediaUrl,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let mut medias: Vec<MediaForPrepare> = Vec::new(); let mut medias: Vec<MediaForPrepare> = Vec::new();
let mut join_set = JoinSet::new(); let mut join_set = JoinSet::new();
@@ -246,16 +246,16 @@ impl VideoTask {
Ok(()) Ok(())
} }
fn prepare(&mut self, app: &AppHandle, medias: &[MediaForPrepare]) -> anyhow::Result<()> { fn prepare(&mut self, app: &AppHandle, medias: &[MediaForPrepare]) -> eyre::Result<()> {
if medias.is_empty() { if medias.is_empty() {
return Err(anyhow!("获取视频地址失败,medias为空")); return Err(eyre!("获取视频地址失败,medias为空"));
} }
let video_quality_is_unknown = self.video_quality == VideoQuality::Unknown; let video_quality_is_unknown = self.video_quality == VideoQuality::Unknown;
let codec_type_is_unknown = self.codec_type == CodecType::Unknown; let codec_type_is_unknown = self.codec_type == CodecType::Unknown;
if video_quality_is_unknown != codec_type_is_unknown { if video_quality_is_unknown != codec_type_is_unknown {
return Err(anyhow!( return Err(eyre!(
"`video_quality`和`codec_type`必须同时为`Unknown`或同时不为`Unknown`" "`video_quality`和`codec_type`必须同时为`Unknown`或同时不为`Unknown`"
)); ));
} }
@@ -269,7 +269,7 @@ impl VideoTask {
select_exact_match_media(self, medias).or_else(|| select_media_by_priority(app, medias)) select_exact_match_media(self, medias).or_else(|| select_media_by_priority(app, medias))
}; };
let media = selected_media.context("获取视频地址失败,medias为空")?; let media = selected_media.ok_or_eyre("获取视频地址失败,medias为空")?;
self.video_quality = media.id.into(); self.video_quality = media.id.into();
self.codec_type = media.codecid.into(); self.codec_type = media.codecid.into();
@@ -322,7 +322,7 @@ impl VideoTask {
&self, &self,
download_task: &Arc<DownloadTask>, download_task: &Arc<DownloadTask>,
progress: &DownloadProgress, progress: &DownloadProgress,
) -> anyhow::Result<()> { ) -> eyre::Result<()> {
let (episode_dir, filename) = (&progress.episode_dir, &progress.filename); let (episode_dir, filename) = (&progress.episode_dir, &progress.filename);
let (video_task, episode_title, ids_string) = { let (video_task, episode_title, ids_string) = {
let progress = download_task.progress.read(); let progress = download_task.progress.read();
@@ -389,7 +389,7 @@ impl VideoTask {
let chunk_order = i + 1; let chunk_order = i + 1;
join_set.spawn(async move { join_set.spawn(async move {
download_chunk_task.process().await.context(format!( download_chunk_task.process().await.wrap_err(format!(
"分片`{chunk_order}/{chunk_count}`下载失败({start}-{end})" "分片`{chunk_order}/{chunk_count}`下载失败({start}-{end})"
)) ))
}); });
@@ -418,20 +418,20 @@ impl VideoTask {
.iter() .iter()
.all(|chunk| chunk.completed); .all(|chunk| chunk.completed);
if !download_completed { if !download_completed {
return Err(anyhow!( return Err(eyre!(
"视频文件`{}`有分片未下载完成,[继续]可以跳过已下载分片断点续传", "视频文件`{}`有分片未下载完成,[继续]可以跳过已下载分片断点续传",
temp_file_path.display() temp_file_path.display()
)); ));
} }
let is_video_file_complete = utils::is_mp4_complete(&temp_file_path).context(format!( let is_video_file_complete = utils::is_mp4_complete(&temp_file_path).wrap_err(format!(
"检查视频文件`{}`是否完整失败", "检查视频文件`{}`是否完整失败",
temp_file_path.display() temp_file_path.display()
))?; ))?;
if !is_video_file_complete { if !is_video_file_complete {
download_task.update_progress(|p| p.video_task.mark_uncompleted()); download_task.update_progress(|p| p.video_task.mark_uncompleted());
return Err(anyhow!( return Err(eyre!(
"视频文件`{}`不完整,[继续]会重新下载所有分片", "视频文件`{}`不完整,[继续]会重新下载所有分片",
temp_file_path.display() temp_file_path.display()
)); ));
@@ -440,9 +440,9 @@ impl VideoTask {
// 重命名临时文件 // 重命名临时文件
if mp4_path.exists() { if mp4_path.exists() {
std::fs::remove_file(&mp4_path) std::fs::remove_file(&mp4_path)
.context(format!("删除已存在的视频文件`{}`失败", mp4_path.display()))?; .wrap_err(format!("删除已存在的视频文件`{}`失败", mp4_path.display()))?;
} }
std::fs::rename(&temp_file_path, &mp4_path).context(format!( std::fs::rename(&temp_file_path, &mp4_path).wrap_err(format!(
"将临时文件`{}`重命名为`{}`失败", "将临时文件`{}`重命名为`{}`失败",
temp_file_path.display(), temp_file_path.display(),
mp4_path.display() mp4_path.display()
+2 -2
View File
@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
use crate::extensions::AnyhowErrorToStringChain; use crate::extensions::EyreToStringChain;
pub type CommandResult<T> = Result<T, CommandError>; pub type CommandResult<T> = Result<T, CommandError>;
@@ -14,7 +14,7 @@ pub struct CommandError {
impl CommandError { impl CommandError {
pub fn from<E>(err_title: &str, err: E) -> Self pub fn from<E>(err_title: &str, err: E) -> Self
where where
E: Into<anyhow::Error>, E: Into<eyre::Report>,
{ {
let string_chain = err.into().to_string_chain(); let string_chain = err.into().to_string_chain();
tracing::error!(err_title, message = string_chain); tracing::error!(err_title, message = string_chain);
+7 -7
View File
@@ -1,4 +1,4 @@
use anyhow::Context; use eyre::WrapErr;
use parking_lot::RwLock; use parking_lot::RwLock;
use tauri::{AppHandle, Manager, State}; use tauri::{AppHandle, Manager, State};
@@ -9,8 +9,8 @@ use crate::{
types::player_info::PlayerInfo, types::player_info::PlayerInfo,
}; };
pub trait AnyhowErrorToStringChain { pub trait EyreToStringChain {
/// 将 `anyhow::Error` 转换为chain格式 /// 将 `eyre::Report` 转换为chain格式
/// # Example /// # Example
/// 0: error message\ /// 0: error message\
/// 1: error message\ /// 1: error message\
@@ -18,7 +18,7 @@ pub trait AnyhowErrorToStringChain {
fn to_string_chain(&self) -> String; fn to_string_chain(&self) -> String;
} }
impl AnyhowErrorToStringChain for anyhow::Error { impl EyreToStringChain for eyre::Report {
fn to_string_chain(&self) -> String { fn to_string_chain(&self) -> String {
use std::fmt::Write; use std::fmt::Write;
self.chain() self.chain()
@@ -53,7 +53,7 @@ pub trait GetOrInitPlayerInfo {
&'a mut self, &'a mut self,
app: &AppHandle, app: &AppHandle,
progress: &DownloadProgress, progress: &DownloadProgress,
) -> anyhow::Result<&'a mut PlayerInfo>; ) -> eyre::Result<&'a mut PlayerInfo>;
} }
impl GetOrInitPlayerInfo for Option<PlayerInfo> { impl GetOrInitPlayerInfo for Option<PlayerInfo> {
@@ -61,7 +61,7 @@ impl GetOrInitPlayerInfo for Option<PlayerInfo> {
&'a mut self, &'a mut self,
app: &AppHandle, app: &AppHandle,
progress: &DownloadProgress, progress: &DownloadProgress,
) -> anyhow::Result<&'a mut PlayerInfo> { ) -> eyre::Result<&'a mut PlayerInfo> {
if let Some(info) = self { if let Some(info) = self {
return Ok(info); return Ok(info);
} }
@@ -70,7 +70,7 @@ impl GetOrInitPlayerInfo for Option<PlayerInfo> {
let info = bili_client let info = bili_client
.get_player_info(progress.aid, progress.cid) .get_player_info(progress.aid, progress.cid)
.await .await
.context("获取播放器信息失败")?; .wrap_err("获取播放器信息失败")?;
Ok(self.insert(info)) Ok(self.insert(info))
} }
+3 -3
View File
@@ -15,7 +15,6 @@ mod protobuf {
include!("./bilibili.community.service.dm.v1.rs"); include!("./bilibili.community.service.dm.v1.rs");
} }
use anyhow::Context;
use commands::{ use commands::{
create_download_tasks, delete_download_tasks, generate_qrcode, get_available_media_formats, create_download_tasks, delete_download_tasks, generate_qrcode, get_available_media_formats,
get_bangumi_follow_info, get_bangumi_info, get_config, get_fav_folders, get_fav_info, get_bangumi_follow_info, get_bangumi_info, get_config, get_fav_folders, get_fav_info,
@@ -25,6 +24,7 @@ use commands::{
save_config, search, show_path_in_file_manager, save_config, search, show_path_in_file_manager,
}; };
use config::Config; use config::Config;
use eyre::WrapErr;
use parking_lot::RwLock; use parking_lot::RwLock;
use tauri::{Manager, Wry}; use tauri::{Manager, Wry};
@@ -99,9 +99,9 @@ pub fn run() {
let app_data_dir = app let app_data_dir = app
.path() .path()
.app_data_dir() .app_data_dir()
.context("获取app_data_dir目录失败")?; .wrap_err("获取app_data_dir目录失败")?;
std::fs::create_dir_all(&app_data_dir).context(format!( std::fs::create_dir_all(&app_data_dir).wrap_err(format!(
"创建app_data_dir目录`{:?}`失败", "创建app_data_dir目录`{:?}`失败",
app_data_dir.display() app_data_dir.display()
))?; ))?;
+20 -20
View File
@@ -1,6 +1,6 @@
use std::{io::Write, sync::OnceLock}; use std::{io::Write, sync::OnceLock};
use anyhow::Context; use eyre::{OptionExt, WrapErr};
use notify::{RecommendedWatcher, Watcher}; use notify::{RecommendedWatcher, Watcher};
use tauri::{AppHandle, Manager}; use tauri::{AppHandle, Manager};
use tauri_specta::Event; use tauri_specta::Event;
@@ -20,7 +20,7 @@ use tracing_subscriber::{
use crate::{ use crate::{
events::LogEvent, events::LogEvent,
extensions::{AnyhowErrorToStringChain, AppHandleExt}, extensions::{AppHandleExt, EyreToStringChain},
}; };
struct LogEventWriter { struct LogEventWriter {
@@ -48,12 +48,12 @@ impl Write for LogEventWriter {
} }
} }
static RELOAD_FN: OnceLock<Box<dyn Fn() -> anyhow::Result<()> + Send + Sync>> = OnceLock::new(); static RELOAD_FN: OnceLock<Box<dyn Fn() -> eyre::Result<()> + Send + Sync>> = OnceLock::new();
static GUARD: OnceLock<parking_lot::Mutex<Option<WorkerGuard>>> = OnceLock::new(); static GUARD: OnceLock<parking_lot::Mutex<Option<WorkerGuard>>> = OnceLock::new();
pub fn init(app: &AppHandle) -> anyhow::Result<()> { pub fn init(app: &AppHandle) -> eyre::Result<()> {
let lib_module_path = module_path!(); let lib_module_path = module_path!();
let lib_target = lib_module_path.split("::").next().context(format!( let lib_target = lib_module_path.split("::").next().ok_or_eyre(format!(
"解析lib_target失败: lib_module_path={lib_module_path}" "解析lib_target失败: lib_module_path={lib_module_path}"
))?; ))?;
// 过滤掉来自其他库的日志 // 过滤掉来自其他库的日志
@@ -92,8 +92,8 @@ pub fn init(app: &AppHandle) -> anyhow::Result<()> {
let app = app.clone(); let app = app.clone();
Box::new(move || { Box::new(move || {
let (file_layer, guard) = create_file_layer(&app)?; let (file_layer, guard) = create_file_layer(&app)?;
reload_handle.reload(file_layer).context("reload失败")?; reload_handle.reload(file_layer).wrap_err("reload失败")?;
*GUARD.get().context("GUARD未初始化")?.lock() = guard; *GUARD.get().ok_or_eyre("GUARD未初始化")?.lock() = guard;
Ok(()) Ok(())
}) })
}); });
@@ -102,12 +102,12 @@ pub fn init(app: &AppHandle) -> anyhow::Result<()> {
Ok(()) Ok(())
} }
pub fn reload_file_logger() -> anyhow::Result<()> { pub fn reload_file_logger() -> eyre::Result<()> {
RELOAD_FN.get().context("RELOAD_FN未初始化")?() RELOAD_FN.get().ok_or_eyre("RELOAD_FN未初始化")?()
} }
pub fn disable_file_logger() -> anyhow::Result<()> { pub fn disable_file_logger() -> eyre::Result<()> {
if let Some(guard) = GUARD.get().context("GUARD未初始化")?.lock().take() { if let Some(guard) = GUARD.get().ok_or_eyre("GUARD未初始化")?.lock().take() {
drop(guard); drop(guard);
} }
Ok(()) Ok(())
@@ -115,7 +115,7 @@ pub fn disable_file_logger() -> anyhow::Result<()> {
fn create_file_layer<S>( fn create_file_layer<S>(
app: &AppHandle, app: &AppHandle,
) -> anyhow::Result<(Box<dyn Layer<S> + Send + Sync>, Option<WorkerGuard>)> ) -> eyre::Result<(Box<dyn Layer<S> + Send + Sync>, Option<WorkerGuard>)>
where where
S: Subscriber + for<'a> LookupSpan<'a>, S: Subscriber + for<'a> LookupSpan<'a>,
{ {
@@ -130,13 +130,13 @@ where
.with_line_number(true); .with_line_number(true);
return Ok((Box::new(sink_layer), None)); return Ok((Box::new(sink_layer), None));
} }
let logs_dir = logs_dir(app).context("获取日志目录失败")?; let logs_dir = logs_dir(app).wrap_err("获取日志目录失败")?;
let file_appender = RollingFileAppender::builder() let file_appender = RollingFileAppender::builder()
.filename_prefix("bilibili-video-downloader") .filename_prefix("bilibili-video-downloader")
.filename_suffix("log") .filename_suffix("log")
.rotation(Rotation::DAILY) .rotation(Rotation::DAILY)
.build(&logs_dir) .build(&logs_dir)
.context("创建RollingFileAppender失败")?; .wrap_err("创建RollingFileAppender失败")?;
let (non_blocking_appender, guard) = tracing_appender::non_blocking(file_appender); let (non_blocking_appender, guard) = tracing_appender::non_blocking(file_appender);
let file_layer = layer() let file_layer = layer()
.with_writer(non_blocking_appender) .with_writer(non_blocking_appender)
@@ -152,7 +152,7 @@ async fn file_log_watcher(app: AppHandle) {
let event_handler = move |res| { let event_handler = move |res| {
tauri::async_runtime::block_on(async { tauri::async_runtime::block_on(async {
if let Err(err) = sender.send(res).await.map_err(anyhow::Error::from) { if let Err(err) = sender.send(res).await.map_err(eyre::Report::from) {
let err_title = "发送日志文件watcher事件失败"; let err_title = "发送日志文件watcher事件失败";
let string_chain = err.to_string_chain(); let string_chain = err.to_string_chain();
tracing::error!(err_title, message = string_chain); tracing::error!(err_title, message = string_chain);
@@ -161,7 +161,7 @@ async fn file_log_watcher(app: AppHandle) {
}; };
let mut watcher = match RecommendedWatcher::new(event_handler, notify::Config::default()) let mut watcher = match RecommendedWatcher::new(event_handler, notify::Config::default())
.map_err(anyhow::Error::from) .map_err(eyre::Report::from)
{ {
Ok(watcher) => watcher, Ok(watcher) => watcher,
Err(err) => { Err(err) => {
@@ -184,7 +184,7 @@ async fn file_log_watcher(app: AppHandle) {
if let Err(err) = watcher if let Err(err) = watcher
.watch(&logs_dir, notify::RecursiveMode::NonRecursive) .watch(&logs_dir, notify::RecursiveMode::NonRecursive)
.map_err(anyhow::Error::from) .map_err(eyre::Report::from)
{ {
let err_title = "日志文件watcher监听日志目录失败"; let err_title = "日志文件watcher监听日志目录失败";
let string_chain = err.to_string_chain(); let string_chain = err.to_string_chain();
@@ -193,7 +193,7 @@ async fn file_log_watcher(app: AppHandle) {
} }
while let Some(res) = receiver.recv().await { while let Some(res) = receiver.recv().await {
match res.map_err(anyhow::Error::from) { match res.map_err(eyre::Report::from) {
Ok(event) => { Ok(event) => {
if let notify::EventKind::Remove(_) = event.kind if let notify::EventKind::Remove(_) = event.kind
&& let Err(err) = reload_file_logger() && let Err(err) = reload_file_logger()
@@ -212,10 +212,10 @@ async fn file_log_watcher(app: AppHandle) {
} }
} }
pub fn logs_dir(app: &AppHandle) -> anyhow::Result<std::path::PathBuf> { pub fn logs_dir(app: &AppHandle) -> eyre::Result<std::path::PathBuf> {
let app_data_dir = app let app_data_dir = app
.path() .path()
.app_data_dir() .app_data_dir()
.context("获取app_data_dir目录失败")?; .wrap_err("获取app_data_dir目录失败")?;
Ok(app_data_dir.join("日志")) Ok(app_data_dir.join("日志"))
} }
+5 -5
View File
@@ -1,4 +1,4 @@
use anyhow::{Context, anyhow}; use eyre::{OptionExt, eyre};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specta::Type; use specta::Type;
@@ -55,7 +55,7 @@ pub struct BangumiInfo {
impl BangumiInfo { impl BangumiInfo {
#[allow(clippy::cast_possible_wrap)] #[allow(clippy::cast_possible_wrap)]
pub fn get_episode_with_order(&self, ep_id: i64) -> anyhow::Result<(&EpInBangumi, i64)> { pub fn get_episode_with_order(&self, ep_id: i64) -> eyre::Result<(&EpInBangumi, i64)> {
let episode_with_order = self let episode_with_order = self
.episodes .episodes
.iter() .iter()
@@ -69,19 +69,19 @@ impl BangumiInfo {
} else { } else {
// 如果在正片中没有找到对应的ep_id,则在section中查找 // 如果在正片中没有找到对应的ep_id,则在section中查找
let Some(sections) = &self.section else { let Some(sections) = &self.section else {
return Err(anyhow!("找不到对应的ep_id为`{ep_id}`的番剧")); return Err(eyre!("找不到对应的ep_id为`{ep_id}`的番剧"));
}; };
let section_index = sections let section_index = sections
.iter() .iter()
.position(|s| s.episodes.iter().any(|e| e.id == ep_id)) .position(|s| s.episodes.iter().any(|e| e.id == ep_id))
.context(format!("找不到含有ep_id为`{ep_id}`的ep的section"))?; .ok_or_eyre(format!("找不到含有ep_id为`{ep_id}`的ep的section"))?;
sections[section_index] sections[section_index]
.episodes .episodes
.iter() .iter()
.enumerate() .enumerate()
.map(|(i, e)| (e, i as i64 + 1)) .map(|(i, e)| (e, i as i64 + 1))
.find(|(e, _)| e.id == ep_id) .find(|(e, _)| e.id == ep_id)
.context(format!("在section中找不到ep_id为`{ep_id}`的ep"))? .ok_or_eyre(format!("在section中找不到ep_id为`{ep_id}`的ep"))?
}; };
Ok(episode_with_order) Ok(episode_with_order)
+12 -12
View File
@@ -4,8 +4,8 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use anyhow::{Context, anyhow};
use byteorder::{BigEndian, ReadBytesExt}; use byteorder::{BigEndian, ReadBytesExt};
use eyre::{OptionExt, WrapErr, eyre};
use crate::{ use crate::{
danmaku_xml_to_ass::{DamakuXmlDTag, DanmakuXmlITag}, danmaku_xml_to_ass::{DamakuXmlDTag, DanmakuXmlITag},
@@ -48,11 +48,11 @@ impl From<u32> for BoxSizeField {
} }
} }
pub fn is_mp4_complete(file_path: &Path) -> anyhow::Result<bool> { pub fn is_mp4_complete(file_path: &Path) -> eyre::Result<bool> {
let file = File::open(file_path).context(format!("打开文件`{}`失败", file_path.display()))?; let file = File::open(file_path).wrap_err(format!("打开文件`{}`失败", file_path.display()))?;
let real_size = file let real_size = file
.metadata() .metadata()
.context(format!("获取文件`{}`元数据失败", file_path.display()))? .wrap_err(format!("获取文件`{}`元数据失败", file_path.display()))?
.len(); .len();
let mut reader = BufReader::new(file); let mut reader = BufReader::new(file);
let mut total_size: u64 = 0; let mut total_size: u64 = 0;
@@ -65,7 +65,7 @@ pub fn is_mp4_complete(file_path: &Path) -> anyhow::Result<bool> {
let box_size_field: BoxSizeField = match reader.read_u32::<BigEndian>() { let box_size_field: BoxSizeField = match reader.read_u32::<BigEndian>() {
Ok(s) => s.into(), Ok(s) => s.into(),
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break, // 正常结束 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break, // 正常结束
Err(e) => return Err(anyhow!(e)), Err(e) => return Err(eyre!(e)),
}; };
// 读取Box类型字段 // 读取Box类型字段
let mut box_type_bytes = [0u8; 4]; let mut box_type_bytes = [0u8; 4];
@@ -74,7 +74,7 @@ pub fn is_mp4_complete(file_path: &Path) -> anyhow::Result<bool> {
if e.kind() == std::io::ErrorKind::UnexpectedEof { if e.kind() == std::io::ErrorKind::UnexpectedEof {
return Ok(false); return Ok(false);
} }
return Err(anyhow!(e)); return Err(eyre!(e));
} }
// 如果是第一个Box,检查是否是 'ftyp' Box // 如果是第一个Box,检查是否是 'ftyp' Box
if is_first_box { if is_first_box {
@@ -131,11 +131,11 @@ pub fn is_mp4_complete(file_path: &Path) -> anyhow::Result<bool> {
} }
pub trait ToXml { pub trait ToXml {
fn to_xml(&self, cid: i64) -> anyhow::Result<String>; fn to_xml(&self, cid: i64) -> eyre::Result<String>;
} }
impl ToXml for Vec<DmSegMobileReply> { impl ToXml for Vec<DmSegMobileReply> {
fn to_xml(&self, cid: i64) -> anyhow::Result<String> { fn to_xml(&self, cid: i64) -> eyre::Result<String> {
let elems = self let elems = self
.iter() .iter()
.flat_map(|reply| &reply.elems) .flat_map(|reply| &reply.elems)
@@ -157,7 +157,7 @@ impl ToXml for Vec<DmSegMobileReply> {
let i_tag = DanmakuXmlITag { chatid: cid, elems }; let i_tag = DanmakuXmlITag { chatid: cid, elems };
let xml = yaserde::ser::to_string(&i_tag).map_err(|e| anyhow!(e))?; let xml = yaserde::ser::to_string(&i_tag).map_err(|e| eyre!(e))?;
Ok(xml) Ok(xml)
} }
@@ -177,11 +177,11 @@ pub fn seconds_to_srt_time(seconds: f64) -> String {
format!("{h:02}:{m:02}:{s:02},{ms:03}") format!("{h:02}:{m:02}:{s:02},{ms:03}")
} }
pub fn get_ffmpeg_program() -> anyhow::Result<PathBuf> { pub fn get_ffmpeg_program() -> eyre::Result<PathBuf> {
let ffmpeg_program = std::env::current_exe() let ffmpeg_program = std::env::current_exe()
.context("获取当前可执行文件路径失败")? .wrap_err("获取当前可执行文件路径失败")?
.parent() .parent()
.context("获取当前可执行文件所在目录失败")? .ok_or_eyre("获取当前可执行文件所在目录失败")?
.join("com.lanyeeee.bilibili-video-downloader-ffmpeg"); .join("com.lanyeeee.bilibili-video-downloader-ffmpeg");
Ok(ffmpeg_program) Ok(ffmpeg_program)
+11 -11
View File
@@ -1,6 +1,6 @@
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use anyhow::{Context, anyhow}; use eyre::{OptionExt, WrapErr, eyre};
use md5::{Digest, Md5}; use md5::{Digest, Md5};
use serde::Deserialize; use serde::Deserialize;
@@ -25,8 +25,8 @@ struct WeiRespData {
impl BiliClient { impl BiliClient {
// 为请求参数进行 wbi 签名 // 为请求参数进行 wbi 签名
pub(crate) async fn wbi(&self, params: &mut Vec<(&str, String)>) -> anyhow::Result<()> { pub(crate) async fn wbi(&self, params: &mut Vec<(&str, String)>) -> eyre::Result<()> {
let (img_key, sub_key) = self.get_wbi_keys().await.context("获取wbi keys失败")?; 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()); let mixin_key = get_mixin_key((img_key + &sub_key).as_bytes());
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); let timestamp = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();
@@ -46,7 +46,7 @@ impl BiliClient {
Ok(()) Ok(())
} }
async fn get_wbi_keys(&self) -> anyhow::Result<(String, String)> { async fn get_wbi_keys(&self) -> eyre::Result<(String, String)> {
let request = self let request = self
.api_client .api_client
.read() .read()
@@ -58,27 +58,27 @@ impl BiliClient {
let status = http_resp.status(); let status = http_resp.status();
let body = http_resp.text().await?; let body = http_resp.text().await?;
if status != reqwest::StatusCode::OK { if status != reqwest::StatusCode::OK {
return Err(anyhow!("预料之外的状态码({status}): {body}")); return Err(eyre!("预料之外的状态码({status}): {body}"));
} }
// 尝试将body解析为BiliResp // 尝试将body解析为BiliResp
let bili_resp: BiliResp = let bili_resp: BiliResp =
serde_json::from_str(&body).context(format!("将body解析为BiliResp失败: {body}"))?; serde_json::from_str(&body).wrap_err(format!("将body解析为BiliResp失败: {body}"))?;
// 检查BiliResp的data是否存在 // 检查BiliResp的data是否存在
let Some(data) = bili_resp.data else { let Some(data) = bili_resp.data else {
return Err(anyhow!("BiliResp中不存在data字段: {bili_resp:?}")); return Err(eyre!("BiliResp中不存在data字段: {bili_resp:?}"));
}; };
// 尝试将data解析为Data // 尝试将data解析为Data
let data_str = data.to_string(); let data_str = data.to_string();
let wei_resp_data: WeiRespData = let wei_resp_data: WeiRespData = serde_json::from_str(&data_str)
serde_json::from_str(&data_str).context(format!("将data解析为Data失败: {data_str}"))?; .wrap_err(format!("将data解析为Data失败: {data_str}"))?;
let img_url = wei_resp_data.wbi_img.img_url; let img_url = wei_resp_data.wbi_img.img_url;
let sub_url = wei_resp_data.wbi_img.sub_url; let sub_url = wei_resp_data.wbi_img.sub_url;
let img_filename = let img_filename =
take_filename(&img_url).context(format!("从img_url中提取文件名失败: {img_url}"))?; take_filename(&img_url).ok_or_eyre(format!("从img_url中提取文件名失败: {img_url}"))?;
let sub_filename = let sub_filename =
take_filename(&sub_url).context(format!("从sub_url中提取文件名失败: {sub_url}"))?; take_filename(&sub_url).ok_or_eyre(format!("从sub_url中提取文件名失败: {sub_url}"))?;
Ok((img_filename, sub_filename)) Ok((img_filename, sub_filename))
} }