mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-09-06 07:57:12 +08:00
feat: 确保 video stream 在出现错误时返回 Err (#231)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_stream::stream;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_stream::try_stream;
|
||||
use futures::Stream;
|
||||
use reqwest::Method;
|
||||
use serde::Deserialize;
|
||||
@@ -162,35 +162,30 @@ impl<'a> Collection<'a> {
|
||||
.validate()
|
||||
}
|
||||
|
||||
pub fn into_simple_video_stream(self) -> impl Stream<Item = VideoInfo> + 'a {
|
||||
stream! {
|
||||
pub fn into_video_stream(self) -> impl Stream<Item = Result<VideoInfo>> + 'a {
|
||||
try_stream! {
|
||||
let mut page = 1;
|
||||
loop {
|
||||
let mut videos = match self.get_videos(page).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"failed to get videos of collection {:?} page {}: {}",
|
||||
self.collection, page, e
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
let mut videos = self.get_videos(page).await.with_context(|| {
|
||||
format!(
|
||||
"failed to get videos of collection {:?} page {}",
|
||||
self.collection, page
|
||||
)
|
||||
})?;
|
||||
let archives = &mut videos["data"]["archives"];
|
||||
if archives.as_array().is_none_or(|v| v.is_empty()) {
|
||||
error!("no videos found in collection {:?} page {}", self.collection, page);
|
||||
break;
|
||||
Err(anyhow!(
|
||||
"no videos found in collection {:?} page {}",
|
||||
self.collection,
|
||||
page
|
||||
))?;
|
||||
}
|
||||
let videos_info: Vec<VideoInfo> = match serde_json::from_value(archives.take()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"failed to parse videos of collection {:?} page {}: {}",
|
||||
self.collection, page, e
|
||||
);
|
||||
break;
|
||||
}
|
||||
};
|
||||
let videos_info: Vec<VideoInfo> = serde_json::from_value(archives.take()).with_context(|| {
|
||||
format!(
|
||||
"failed to parse videos of collection {:?} page {}",
|
||||
self.collection, page
|
||||
)
|
||||
})?;
|
||||
for video_info in videos_info {
|
||||
yield video_info;
|
||||
}
|
||||
@@ -199,17 +194,23 @@ impl<'a> Collection<'a> {
|
||||
CollectionType::Series => ["num", "size", "total"],
|
||||
CollectionType::Season => ["page_num", "page_size", "total"],
|
||||
};
|
||||
let values = fields.iter().map(|f| page_info[f].as_i64()).collect::<Vec<Option<i64>>>();
|
||||
let values = fields
|
||||
.iter()
|
||||
.map(|f| page_info[f].as_i64())
|
||||
.collect::<Vec<Option<i64>>>();
|
||||
if let [Some(num), Some(size), Some(total)] = values[..] {
|
||||
if num * size < total {
|
||||
page += 1;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
error!(
|
||||
Err(anyhow!(
|
||||
"invalid page info of collection {:?} page {}: read {:?} from {}",
|
||||
self.collection, page, fields, page_info
|
||||
);
|
||||
self.collection,
|
||||
page,
|
||||
fields,
|
||||
page_info
|
||||
))?;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use async_stream::stream;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_stream::try_stream;
|
||||
use futures::Stream;
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -62,36 +62,31 @@ impl<'a> FavoriteList<'a> {
|
||||
}
|
||||
|
||||
// 拿到收藏夹的所有权,返回一个收藏夹下的视频流
|
||||
pub fn into_video_stream(self) -> impl Stream<Item = VideoInfo> + 'a {
|
||||
stream! {
|
||||
pub fn into_video_stream(self) -> impl Stream<Item = Result<VideoInfo>> + 'a {
|
||||
try_stream! {
|
||||
let mut page = 1;
|
||||
loop {
|
||||
let mut videos = match self.get_videos(page).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed to get videos of favorite {} page {}: {}", self.fid, page, e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
let mut videos = self
|
||||
.get_videos(page)
|
||||
.await
|
||||
.with_context(|| format!("failed to get videos of favorite {} page {}", self.fid, page))?;
|
||||
let medias = &mut videos["data"]["medias"];
|
||||
if medias.as_array().is_none_or(|v| v.is_empty()) {
|
||||
error!("no medias found in favorite {} page {}", self.fid, page);
|
||||
break;
|
||||
Err(anyhow!("no medias found in favorite {} page {}", self.fid, page))?;
|
||||
}
|
||||
let videos_info: Vec<VideoInfo> = match serde_json::from_value(medias.take()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed to parse videos of favorite {} page {}: {}", self.fid, page, e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
let videos_info: Vec<VideoInfo> = serde_json::from_value(medias.take())
|
||||
.with_context(|| format!("failed to parse videos of favorite {} page {}", self.fid, page))?;
|
||||
for video_info in videos_info {
|
||||
yield video_info;
|
||||
}
|
||||
let has_more = &videos["data"]["has_more"];
|
||||
if has_more.as_bool().is_some_and(|v| v) {
|
||||
page += 1;
|
||||
continue;
|
||||
if let Some(v) = has_more.as_bool() {
|
||||
if v {
|
||||
page += 1;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!("has_more is not a bool"))?;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -158,22 +158,42 @@ mod tests {
|
||||
collection_type: CollectionType::Season,
|
||||
};
|
||||
let collection = Collection::new(&bili_client, &collection_item);
|
||||
let videos = collection.into_simple_video_stream().take(20).collect::<Vec<_>>().await;
|
||||
let videos = collection
|
||||
.into_video_stream()
|
||||
.take(20)
|
||||
.filter_map(|v| futures::future::ready(v.ok()))
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
assert!(videos.iter().all(|v| matches!(v, VideoInfo::Collection { .. })));
|
||||
assert!(videos.iter().rev().is_sorted_by_key(|v| v.release_datetime()));
|
||||
// 测试收藏夹
|
||||
let favorite = FavoriteList::new(&bili_client, "3144336058".to_string());
|
||||
let videos = favorite.into_video_stream().take(20).collect::<Vec<_>>().await;
|
||||
let videos = favorite
|
||||
.into_video_stream()
|
||||
.take(20)
|
||||
.filter_map(|v| futures::future::ready(v.ok()))
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
assert!(videos.iter().all(|v| matches!(v, VideoInfo::Favorite { .. })));
|
||||
assert!(videos.iter().rev().is_sorted_by_key(|v| v.release_datetime()));
|
||||
// 测试稍后再看
|
||||
let watch_later = WatchLater::new(&bili_client);
|
||||
let videos = watch_later.into_video_stream().take(20).collect::<Vec<_>>().await;
|
||||
let videos = watch_later
|
||||
.into_video_stream()
|
||||
.take(20)
|
||||
.filter_map(|v| futures::future::ready(v.ok()))
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
assert!(videos.iter().all(|v| matches!(v, VideoInfo::WatchLater { .. })));
|
||||
assert!(videos.iter().rev().is_sorted_by_key(|v| v.release_datetime()));
|
||||
// 测试投稿
|
||||
let submission = Submission::new(&bili_client, "956761".to_string());
|
||||
let videos = submission.into_video_stream().take(20).collect::<Vec<_>>().await;
|
||||
let videos = submission
|
||||
.into_video_stream()
|
||||
.take(20)
|
||||
.filter_map(|v| futures::future::ready(v.ok()))
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
assert!(videos.iter().all(|v| matches!(v, VideoInfo::Submission { .. })));
|
||||
assert!(videos.iter().rev().is_sorted_by_key(|v| v.release_datetime()));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use arc_swap::access::Access;
|
||||
use async_stream::stream;
|
||||
use async_stream::try_stream;
|
||||
use futures::Stream;
|
||||
use reqwest::Method;
|
||||
use serde_json::Value;
|
||||
@@ -57,36 +57,31 @@ impl<'a> Submission<'a> {
|
||||
.validate()
|
||||
}
|
||||
|
||||
pub fn into_video_stream(self) -> impl Stream<Item = VideoInfo> + 'a {
|
||||
stream! {
|
||||
pub fn into_video_stream(self) -> impl Stream<Item = Result<VideoInfo>> + 'a {
|
||||
try_stream! {
|
||||
let mut page = 1;
|
||||
loop {
|
||||
let mut videos = match self.get_videos(page).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed to get videos of upper {} page {}: {}", self.upper_id, page, e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
let mut videos = self
|
||||
.get_videos(page)
|
||||
.await
|
||||
.with_context(|| format!("failed to get videos of upper {} page {}", self.upper_id, page))?;
|
||||
let vlist = &mut videos["data"]["list"]["vlist"];
|
||||
if vlist.as_array().is_none_or(|v| v.is_empty()) {
|
||||
error!("no medias found in upper {} page {}", self.upper_id, page);
|
||||
break;
|
||||
Err(anyhow!("no medias found in upper {} page {}", self.upper_id, page))?;
|
||||
}
|
||||
let videos_info: Vec<VideoInfo> = match serde_json::from_value(vlist.take()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("failed to parse videos of upper {} page {}: {}", self.upper_id, page, e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
let videos_info: Vec<VideoInfo> = serde_json::from_value(vlist.take())
|
||||
.with_context(|| format!("failed to parse videos of upper {} page {}", self.upper_id, page))?;
|
||||
for video_info in videos_info {
|
||||
yield video_info;
|
||||
}
|
||||
let count = &videos["data"]["page"]["count"];
|
||||
if count.as_i64().is_some_and(|v| v > (page * 30) as i64) {
|
||||
page += 1;
|
||||
continue;
|
||||
if let Some(v) = count.as_i64() {
|
||||
if v > (page * 30) as i64 {
|
||||
page += 1;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
Err(anyhow!("count is not an i64"))?;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use async_stream::stream;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use async_stream::try_stream;
|
||||
use futures::Stream;
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -25,24 +25,20 @@ impl<'a> WatchLater<'a> {
|
||||
.validate()
|
||||
}
|
||||
|
||||
pub fn into_video_stream(self) -> impl Stream<Item = VideoInfo> + 'a {
|
||||
stream! {
|
||||
let Ok(mut videos) = self.get_videos().await else {
|
||||
error!("Failed to get watch later list");
|
||||
return;
|
||||
};
|
||||
if !videos["data"]["list"].is_array() {
|
||||
error!("Watch later list is not an array");
|
||||
pub fn into_video_stream(self) -> impl Stream<Item = Result<VideoInfo>> + 'a {
|
||||
try_stream! {
|
||||
let mut videos = self
|
||||
.get_videos()
|
||||
.await
|
||||
.with_context(|| "Failed to get watch later list")?;
|
||||
let list = &mut videos["data"]["list"];
|
||||
if list.as_array().is_none_or(|v| v.is_empty()) {
|
||||
Err(anyhow!("No videos found in watch later list"))?;
|
||||
}
|
||||
let videos_info = match serde_json::from_value::<Vec<VideoInfo>>(videos["data"]["list"].take()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("Failed to parse watch later list: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
for video in videos_info {
|
||||
yield video;
|
||||
let videos_info: Vec<VideoInfo> =
|
||||
serde_json::from_value(list.take()).with_context(|| "Failed to parse watch later list")?;
|
||||
for video_info in videos_info {
|
||||
yield video_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user