fix: 修复 video 中分 p 下载状态的设置 (#272)
This commit is contained in:
@@ -16,6 +16,8 @@ pub enum ExecutionStatus {
|
||||
Succeeded,
|
||||
Ignored(anyhow::Error),
|
||||
Failed(anyhow::Error),
|
||||
// 任务可以返回该状态固定自己的 status
|
||||
FixedFailed(u32, anyhow::Error),
|
||||
}
|
||||
|
||||
// 目前 stable rust 似乎不支持自定义类型使用 ? 运算符,只能先在返回值使用 Result,再这样套层娃
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::error::ExecutionStatus;
|
||||
|
||||
pub(super) static STATUS_MAX_RETRY: u32 = 0b100;
|
||||
pub(super) static STATUS_OK: u32 = 0b111;
|
||||
pub static STATUS_OK: u32 = 0b111;
|
||||
pub static STATUS_COMPLETED: u32 = 1 << 31;
|
||||
|
||||
/// 用来表示下载的状态,不想写太多列了,所以仅使用一个 u32 表示。
|
||||
@@ -67,9 +67,10 @@ impl<const N: usize> Status<N> {
|
||||
for (i, res) in result.iter().enumerate() {
|
||||
self.set_result(res, i);
|
||||
}
|
||||
if self.should_run().iter().all(|x| !x) {
|
||||
// 所有任务都成功或者由于尝试次数过多失败,为 status 最高位打上标记,将来不再重试
|
||||
self.set_completed(true)
|
||||
if self.should_run().into_iter().all(|x| !x) {
|
||||
self.set_completed(true);
|
||||
} else {
|
||||
self.set_completed(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,14 +109,18 @@ impl<const N: usize> Status<N> {
|
||||
}
|
||||
|
||||
/// 根据子任务执行结果更新子任务的状态
|
||||
/// 如果 Result 是 Ok,那么认为任务执行成功,将状态设置为 STATUS_OK
|
||||
/// 如果 Result 是 Err,那么认为任务执行失败,将状态加一
|
||||
fn set_result(&mut self, result: &ExecutionStatus, offset: usize) {
|
||||
if self.get_status(offset) < STATUS_MAX_RETRY {
|
||||
match result {
|
||||
ExecutionStatus::Succeeded | ExecutionStatus::Skipped => self.set_ok(offset),
|
||||
ExecutionStatus::Failed(_) => self.plus_one(offset),
|
||||
ExecutionStatus::Ignored(_) => {}
|
||||
// 如果任务返回 FixedFailed 状态,那么无论之前的状态如何,都将状态设置为 FixedFailed 的状态
|
||||
if let ExecutionStatus::FixedFailed(status, _) = result {
|
||||
assert!(*status < 0b1000, "status should be less than 0b1000");
|
||||
self.set_status(offset, *status);
|
||||
} else {
|
||||
if self.get_status(offset) < STATUS_MAX_RETRY {
|
||||
match result {
|
||||
ExecutionStatus::Succeeded | ExecutionStatus::Skipped => self.set_ok(offset),
|
||||
ExecutionStatus::Failed(_) => self.plus_one(offset),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,6 +192,15 @@ mod test {
|
||||
ExecutionStatus::Succeeded,
|
||||
]);
|
||||
assert_eq!(status.should_run(), [false, false, false]);
|
||||
assert!(status.get_completed());
|
||||
status.update_status(&[
|
||||
ExecutionStatus::FixedFailed(1, anyhow!("")),
|
||||
ExecutionStatus::FixedFailed(4, anyhow!("")),
|
||||
ExecutionStatus::FixedFailed(7, anyhow!("")),
|
||||
]);
|
||||
assert_eq!(status.should_run(), [true, false, false]);
|
||||
assert!(!status.get_completed());
|
||||
assert_eq!(<[u32; 3]>::from(status), [1, 4, 7]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::utils::model::{
|
||||
update_videos_model,
|
||||
};
|
||||
use crate::utils::nfo::{ModelWrapper, NFOMode, NFOSerializer};
|
||||
use crate::utils::status::{PageStatus, VideoStatus};
|
||||
use crate::utils::status::{PageStatus, VideoStatus, STATUS_OK};
|
||||
|
||||
/// 完整地处理某个视频来源
|
||||
pub async fn process_video_source(
|
||||
@@ -274,7 +274,9 @@ pub async fn download_video_pages(
|
||||
&video_model.name, task_name, e
|
||||
)
|
||||
}
|
||||
ExecutionStatus::Failed(e) => error!("处理视频「{}」{}失败: {:#}", &video_model.name, task_name, e),
|
||||
ExecutionStatus::Failed(e) | ExecutionStatus::FixedFailed(_, e) => {
|
||||
error!("处理视频「{}」{}失败: {:#}", &video_model.name, task_name, e)
|
||||
}
|
||||
});
|
||||
if let ExecutionStatus::Failed(e) = results.into_iter().nth(4).context("page download result not found")? {
|
||||
if e.downcast_ref::<DownloadAbortError>().is_some() {
|
||||
@@ -314,18 +316,19 @@ pub async fn dispatch_download_page(
|
||||
)
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
let (mut download_aborted, mut error_occurred) = (false, false);
|
||||
let (mut download_aborted, mut target_status) = (false, STATUS_OK);
|
||||
let mut stream = tasks
|
||||
.take_while(|res| {
|
||||
match res {
|
||||
Ok(model) => {
|
||||
// 当前函数返回的是所有分页的下载状态,只要有任何一个分页返回新的下载状态标识位是 false,当前函数就应该认为是失败的
|
||||
if model
|
||||
.download_status
|
||||
.try_as_ref()
|
||||
.is_none_or(|status| !PageStatus::from(*status).get_completed())
|
||||
{
|
||||
error_occurred = true;
|
||||
// 该视频的所有分页的下载状态都会在此返回,需要根据这些状态确认视频层“分 P 下载”子任务的状态
|
||||
// 在过去的实现中,此处仅仅根据 page_download_status 的最高标志位来判断,如果最高标志位是 true 则认为完成
|
||||
// 这样会导致即使分页中有失败到 MAX_RETRY 的情况,视频层的分 P 下载状态也会被认为是 Succeeded,不够准确
|
||||
// 新版本实现会将此处取值为所有子任务状态的最小值,这样只有所有分页的子任务全部成功时才会认为视频层的分 P 下载状态是 Succeeded
|
||||
let page_download_status = model.download_status.try_as_ref().expect("download_status must be set");
|
||||
let separate_status: [u32; 5] = PageStatus::from(*page_download_status).into();
|
||||
for status in separate_status {
|
||||
target_status = target_status.min(status);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -346,12 +349,8 @@ pub async fn dispatch_download_page(
|
||||
error!("下载视频「{}」的分页时触发风控,将异常向上传递..", &video_model.name);
|
||||
bail!(DownloadAbortError());
|
||||
}
|
||||
if error_occurred {
|
||||
error!(
|
||||
"下载视频「{}」的分页时出现错误,将在下一轮尝试重新处理",
|
||||
&video_model.name
|
||||
);
|
||||
bail!(ProcessPageError());
|
||||
if target_status != STATUS_OK {
|
||||
return Ok(ExecutionStatus::FixedFailed(target_status, ProcessPageError().into()));
|
||||
}
|
||||
Ok(ExecutionStatus::Succeeded)
|
||||
}
|
||||
@@ -473,7 +472,7 @@ pub async fn download_page(
|
||||
&video_model.name, page_model.pid, task_name, e
|
||||
)
|
||||
}
|
||||
ExecutionStatus::Failed(e) => error!(
|
||||
ExecutionStatus::Failed(e) | ExecutionStatus::FixedFailed(_, e) => error!(
|
||||
"处理视频「{}」第 {} 页{}失败: {:#}",
|
||||
&video_model.name, page_model.pid, task_name, e
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user