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