refactor: 重构部分代码,调整函数位置 (#154)
This commit is contained in:
@@ -3,105 +3,53 @@ use std::path::Path;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use async_trait::async_trait;
|
||||||
use bili_sync_entity::*;
|
use bili_sync_entity::*;
|
||||||
use bili_sync_migration::OnConflict;
|
|
||||||
use filenamify::filenamify;
|
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
use sea_orm::sea_query::{IntoCondition, OnConflict};
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use sea_orm::{DatabaseConnection, QuerySelect, TransactionTrait};
|
use sea_orm::{DatabaseConnection, TransactionTrait};
|
||||||
|
|
||||||
use crate::adapter::{error_fetch_video_detail, VideoListModel};
|
use crate::adapter::{helper, VideoListModel};
|
||||||
use crate::bilibili::{self, BiliClient, Collection, CollectionItem, CollectionType, VideoInfo};
|
use crate::bilibili::{self, BiliClient, Collection, CollectionItem, CollectionType, VideoInfo};
|
||||||
use crate::config::TEMPLATE;
|
|
||||||
use crate::utils::id_time_key;
|
|
||||||
use crate::utils::model::create_video_pages;
|
|
||||||
use crate::utils::status::Status;
|
use crate::utils::status::Status;
|
||||||
|
|
||||||
pub async fn collection_from<'a>(
|
|
||||||
collection_item: &'a CollectionItem,
|
|
||||||
path: &Path,
|
|
||||||
bili_client: &'a BiliClient,
|
|
||||||
connection: &DatabaseConnection,
|
|
||||||
) -> Result<(Box<dyn VideoListModel>, Pin<Box<dyn Stream<Item = VideoInfo> + 'a>>)> {
|
|
||||||
let collection = Collection::new(bili_client, collection_item);
|
|
||||||
let collection_info = collection.get_info().await?;
|
|
||||||
collection::Entity::insert(collection::ActiveModel {
|
|
||||||
s_id: Set(collection_info.sid),
|
|
||||||
m_id: Set(collection_info.mid),
|
|
||||||
r#type: Set(collection_info.collection_type.into()),
|
|
||||||
name: Set(collection_info.name.clone()),
|
|
||||||
path: Set(path.to_string_lossy().to_string()),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.on_conflict(
|
|
||||||
OnConflict::columns([
|
|
||||||
collection::Column::SId,
|
|
||||||
collection::Column::MId,
|
|
||||||
collection::Column::Type,
|
|
||||||
])
|
|
||||||
.update_columns([collection::Column::Name, collection::Column::Path])
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.exec(connection)
|
|
||||||
.await?;
|
|
||||||
Ok((
|
|
||||||
Box::new(
|
|
||||||
collection::Entity::find()
|
|
||||||
.filter(
|
|
||||||
collection::Column::SId
|
|
||||||
.eq(collection_item.sid.clone())
|
|
||||||
.and(collection::Column::MId.eq(collection_item.mid.clone()))
|
|
||||||
.and(collection::Column::Type.eq(Into::<i32>::into(collection_item.collection_type.clone()))),
|
|
||||||
)
|
|
||||||
.one(connection)
|
|
||||||
.await?
|
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
Box::pin(collection.into_simple_video_stream()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl VideoListModel for collection::Model {
|
impl VideoListModel for collection::Model {
|
||||||
async fn video_count(&self, connection: &DatabaseConnection) -> Result<u64> {
|
async fn video_count(&self, connection: &DatabaseConnection) -> Result<u64> {
|
||||||
Ok(video::Entity::find()
|
helper::count_videos(video::Column::CollectionId.eq(self.id).into_condition(), connection).await
|
||||||
.filter(video::Column::CollectionId.eq(self.id))
|
|
||||||
.count(connection)
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unfilled_videos(&self, connection: &DatabaseConnection) -> Result<Vec<video::Model>> {
|
async fn unfilled_videos(&self, connection: &DatabaseConnection) -> Result<Vec<video::Model>> {
|
||||||
Ok(video::Entity::find()
|
helper::filter_videos(
|
||||||
.filter(
|
video::Column::CollectionId
|
||||||
video::Column::CollectionId
|
.eq(self.id)
|
||||||
.eq(self.id)
|
.and(video::Column::Valid.eq(true))
|
||||||
.and(video::Column::Valid.eq(true))
|
.and(video::Column::DownloadStatus.eq(0))
|
||||||
.and(video::Column::DownloadStatus.eq(0))
|
.and(video::Column::Category.eq(2))
|
||||||
.and(video::Column::Category.eq(2))
|
.and(video::Column::SinglePage.is_null())
|
||||||
.and(video::Column::SinglePage.is_null()),
|
.into_condition(),
|
||||||
)
|
connection,
|
||||||
.all(connection)
|
)
|
||||||
.await?)
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unhandled_video_pages(
|
async fn unhandled_video_pages(
|
||||||
&self,
|
&self,
|
||||||
connection: &DatabaseConnection,
|
connection: &DatabaseConnection,
|
||||||
) -> Result<Vec<(video::Model, Vec<page::Model>)>> {
|
) -> Result<Vec<(video::Model, Vec<page::Model>)>> {
|
||||||
Ok(video::Entity::find()
|
helper::filter_videos_with_pages(
|
||||||
.filter(
|
video::Column::CollectionId
|
||||||
video::Column::CollectionId
|
.eq(self.id)
|
||||||
.eq(self.id)
|
.and(video::Column::Valid.eq(true))
|
||||||
.and(video::Column::Valid.eq(true))
|
.and(video::Column::DownloadStatus.lt(Status::handled()))
|
||||||
.and(video::Column::DownloadStatus.lt(Status::handled()))
|
.and(video::Column::Category.eq(2))
|
||||||
.and(video::Column::Category.eq(2))
|
.and(video::Column::SinglePage.is_not_null())
|
||||||
.and(video::Column::SinglePage.is_not_null()),
|
.into_condition(),
|
||||||
)
|
connection,
|
||||||
.find_with_related(page::Entity)
|
)
|
||||||
.all(connection)
|
.await
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exist_labels(
|
async fn exist_labels(
|
||||||
@@ -109,37 +57,13 @@ impl VideoListModel for collection::Model {
|
|||||||
videos_info: &[VideoInfo],
|
videos_info: &[VideoInfo],
|
||||||
connection: &DatabaseConnection,
|
connection: &DatabaseConnection,
|
||||||
) -> Result<HashSet<String>> {
|
) -> Result<HashSet<String>> {
|
||||||
let bvids = videos_info.iter().map(|v| v.bvid().to_string()).collect::<Vec<_>>();
|
helper::video_keys(videos_info, [video::Column::Bvid, video::Column::Pubtime], connection).await
|
||||||
Ok(video::Entity::find()
|
|
||||||
.filter(
|
|
||||||
video::Column::CollectionId
|
|
||||||
.eq(self.id)
|
|
||||||
.and(video::Column::Bvid.is_in(bvids)),
|
|
||||||
)
|
|
||||||
.select_only()
|
|
||||||
.columns([video::Column::Bvid, video::Column::Pubtime])
|
|
||||||
.into_tuple()
|
|
||||||
.all(connection)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(bvid, time)| id_time_key(&bvid, &time))
|
|
||||||
.collect::<HashSet<_>>())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn video_model_by_info(&self, video_info: &VideoInfo, base_model: Option<video::Model>) -> video::ActiveModel {
|
fn video_model_by_info(&self, video_info: &VideoInfo, base_model: Option<video::Model>) -> video::ActiveModel {
|
||||||
let mut video_model = video_info.to_model(base_model);
|
let mut video_model = video_info.to_model(base_model);
|
||||||
video_model.collection_id = Set(Some(self.id));
|
video_model.collection_id = Set(Some(self.id));
|
||||||
if let Some(fmt_args) = &video_info.to_fmt_args() {
|
helper::video_with_path(video_model, &self.path, video_info)
|
||||||
video_model.path = Set(Path::new(&self.path)
|
|
||||||
.join(filenamify(
|
|
||||||
TEMPLATE
|
|
||||||
.render("video", fmt_args)
|
|
||||||
.unwrap_or_else(|_| video_info.bvid().to_string()),
|
|
||||||
))
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string());
|
|
||||||
}
|
|
||||||
video_model
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_videos_detail(
|
async fn fetch_videos_detail(
|
||||||
@@ -156,7 +80,7 @@ impl VideoListModel for collection::Model {
|
|||||||
};
|
};
|
||||||
let txn = connection.begin().await?;
|
let txn = connection.begin().await?;
|
||||||
// 将分页信息写入数据库
|
// 将分页信息写入数据库
|
||||||
create_video_pages(pages, &video_model, &txn).await?;
|
helper::create_video_pages(pages, &video_model, &txn).await?;
|
||||||
// 将页标记和 tag 写入数据库
|
// 将页标记和 tag 写入数据库
|
||||||
let mut video_active_model = self.video_model_by_info(&view_info, Some(video_model));
|
let mut video_active_model = self.video_model_by_info(&view_info, Some(video_model));
|
||||||
video_active_model.single_page = Set(Some(pages.len() == 1));
|
video_active_model.single_page = Set(Some(pages.len() == 1));
|
||||||
@@ -165,7 +89,7 @@ impl VideoListModel for collection::Model {
|
|||||||
txn.commit().await?;
|
txn.commit().await?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error_fetch_video_detail(e, video_model, connection).await?;
|
helper::error_fetch_video_detail(e, video_model, connection).await?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -227,3 +151,47 @@ impl VideoListModel for collection::Model {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn collection_from<'a>(
|
||||||
|
collection_item: &'a CollectionItem,
|
||||||
|
path: &Path,
|
||||||
|
bili_client: &'a BiliClient,
|
||||||
|
connection: &DatabaseConnection,
|
||||||
|
) -> Result<(Box<dyn VideoListModel>, Pin<Box<dyn Stream<Item = VideoInfo> + 'a>>)> {
|
||||||
|
let collection = Collection::new(bili_client, collection_item);
|
||||||
|
let collection_info = collection.get_info().await?;
|
||||||
|
collection::Entity::insert(collection::ActiveModel {
|
||||||
|
s_id: Set(collection_info.sid),
|
||||||
|
m_id: Set(collection_info.mid),
|
||||||
|
r#type: Set(collection_info.collection_type.into()),
|
||||||
|
name: Set(collection_info.name.clone()),
|
||||||
|
path: Set(path.to_string_lossy().to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.on_conflict(
|
||||||
|
OnConflict::columns([
|
||||||
|
collection::Column::SId,
|
||||||
|
collection::Column::MId,
|
||||||
|
collection::Column::Type,
|
||||||
|
])
|
||||||
|
.update_columns([collection::Column::Name, collection::Column::Path])
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.exec(connection)
|
||||||
|
.await?;
|
||||||
|
Ok((
|
||||||
|
Box::new(
|
||||||
|
collection::Entity::find()
|
||||||
|
.filter(
|
||||||
|
collection::Column::SId
|
||||||
|
.eq(collection_item.sid.clone())
|
||||||
|
.and(collection::Column::MId.eq(collection_item.mid.clone()))
|
||||||
|
.and(collection::Column::Type.eq(Into::<i32>::into(collection_item.collection_type.clone()))),
|
||||||
|
)
|
||||||
|
.one(connection)
|
||||||
|
.await?
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
Box::pin(collection.into_simple_video_stream()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,95 +3,53 @@ use std::path::Path;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use async_trait::async_trait;
|
||||||
use bili_sync_entity::*;
|
use bili_sync_entity::*;
|
||||||
use bili_sync_migration::OnConflict;
|
|
||||||
use filenamify::filenamify;
|
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
use sea_orm::sea_query::{IntoCondition, OnConflict};
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use sea_orm::{DatabaseConnection, QuerySelect, TransactionTrait};
|
use sea_orm::{DatabaseConnection, TransactionTrait};
|
||||||
|
|
||||||
use crate::adapter::{error_fetch_video_detail, VideoListModel};
|
use crate::adapter::{helper, VideoListModel};
|
||||||
use crate::bilibili::{self, BiliClient, FavoriteList, VideoInfo};
|
use crate::bilibili::{self, BiliClient, FavoriteList, VideoInfo};
|
||||||
use crate::config::TEMPLATE;
|
|
||||||
use crate::utils::id_time_key;
|
|
||||||
use crate::utils::model::create_video_pages;
|
|
||||||
use crate::utils::status::Status;
|
use crate::utils::status::Status;
|
||||||
|
|
||||||
pub async fn favorite_from<'a>(
|
|
||||||
fid: &str,
|
|
||||||
path: &Path,
|
|
||||||
bili_client: &'a BiliClient,
|
|
||||||
connection: &DatabaseConnection,
|
|
||||||
) -> Result<(Box<dyn VideoListModel>, Pin<Box<dyn Stream<Item = VideoInfo> + 'a>>)> {
|
|
||||||
let favorite = FavoriteList::new(bili_client, fid.to_owned());
|
|
||||||
let favorite_info = favorite.get_info().await?;
|
|
||||||
favorite::Entity::insert(favorite::ActiveModel {
|
|
||||||
f_id: Set(favorite_info.id),
|
|
||||||
name: Set(favorite_info.title.clone()),
|
|
||||||
path: Set(path.to_string_lossy().to_string()),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.on_conflict(
|
|
||||||
OnConflict::column(favorite::Column::FId)
|
|
||||||
.update_columns([favorite::Column::Name, favorite::Column::Path])
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.exec(connection)
|
|
||||||
.await?;
|
|
||||||
Ok((
|
|
||||||
Box::new(
|
|
||||||
favorite::Entity::find()
|
|
||||||
.filter(favorite::Column::FId.eq(favorite_info.id))
|
|
||||||
.one(connection)
|
|
||||||
.await?
|
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
Box::pin(favorite.into_video_stream()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl VideoListModel for favorite::Model {
|
impl VideoListModel for favorite::Model {
|
||||||
async fn video_count(&self, connection: &DatabaseConnection) -> Result<u64> {
|
async fn video_count(&self, connection: &DatabaseConnection) -> Result<u64> {
|
||||||
Ok(video::Entity::find()
|
helper::count_videos(video::Column::FavoriteId.eq(self.id).into_condition(), connection).await
|
||||||
.filter(video::Column::FavoriteId.eq(self.id))
|
|
||||||
.count(connection)
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unfilled_videos(&self, connection: &DatabaseConnection) -> Result<Vec<video::Model>> {
|
async fn unfilled_videos(&self, connection: &DatabaseConnection) -> Result<Vec<video::Model>> {
|
||||||
Ok(video::Entity::find()
|
helper::filter_videos(
|
||||||
.filter(
|
video::Column::FavoriteId
|
||||||
video::Column::FavoriteId
|
.eq(self.id)
|
||||||
.eq(self.id)
|
.and(video::Column::Valid.eq(true))
|
||||||
.and(video::Column::Valid.eq(true))
|
.and(video::Column::DownloadStatus.eq(0))
|
||||||
.and(video::Column::DownloadStatus.eq(0))
|
.and(video::Column::Category.eq(2))
|
||||||
.and(video::Column::Category.eq(2))
|
.and(video::Column::SinglePage.is_null())
|
||||||
.and(video::Column::SinglePage.is_null()),
|
.into_condition(),
|
||||||
)
|
connection,
|
||||||
.all(connection)
|
)
|
||||||
.await?)
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unhandled_video_pages(
|
async fn unhandled_video_pages(
|
||||||
&self,
|
&self,
|
||||||
connection: &DatabaseConnection,
|
connection: &DatabaseConnection,
|
||||||
) -> Result<Vec<(video::Model, Vec<page::Model>)>> {
|
) -> Result<Vec<(video::Model, Vec<page::Model>)>> {
|
||||||
Ok(video::Entity::find()
|
helper::filter_videos_with_pages(
|
||||||
.filter(
|
video::Column::FavoriteId
|
||||||
video::Column::FavoriteId
|
.eq(self.id)
|
||||||
.eq(self.id)
|
.and(video::Column::Valid.eq(true))
|
||||||
.and(video::Column::Valid.eq(true))
|
.and(video::Column::DownloadStatus.lt(Status::handled()))
|
||||||
.and(video::Column::DownloadStatus.lt(Status::handled()))
|
.and(video::Column::Category.eq(2))
|
||||||
.and(video::Column::Category.eq(2))
|
.and(video::Column::SinglePage.is_not_null())
|
||||||
.and(video::Column::SinglePage.is_not_null()),
|
.into_condition(),
|
||||||
)
|
connection,
|
||||||
.find_with_related(page::Entity)
|
)
|
||||||
.all(connection)
|
.await
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exist_labels(
|
async fn exist_labels(
|
||||||
@@ -99,37 +57,13 @@ impl VideoListModel for favorite::Model {
|
|||||||
videos_info: &[VideoInfo],
|
videos_info: &[VideoInfo],
|
||||||
connection: &DatabaseConnection,
|
connection: &DatabaseConnection,
|
||||||
) -> Result<HashSet<String>> {
|
) -> Result<HashSet<String>> {
|
||||||
let bvids = videos_info.iter().map(|v| v.bvid().to_string()).collect::<Vec<_>>();
|
helper::video_keys(videos_info, [video::Column::Bvid, video::Column::Favtime], connection).await
|
||||||
Ok(video::Entity::find()
|
|
||||||
.filter(
|
|
||||||
video::Column::FavoriteId
|
|
||||||
.eq(self.id)
|
|
||||||
.and(video::Column::Bvid.is_in(bvids)),
|
|
||||||
)
|
|
||||||
.select_only()
|
|
||||||
.columns([video::Column::Bvid, video::Column::Favtime])
|
|
||||||
.into_tuple()
|
|
||||||
.all(connection)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(bvid, time)| id_time_key(&bvid, &time))
|
|
||||||
.collect::<HashSet<_>>())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn video_model_by_info(&self, video_info: &VideoInfo, base_model: Option<video::Model>) -> video::ActiveModel {
|
fn video_model_by_info(&self, video_info: &VideoInfo, base_model: Option<video::Model>) -> video::ActiveModel {
|
||||||
let mut video_model = video_info.to_model(base_model);
|
let mut video_model = video_info.to_model(base_model);
|
||||||
video_model.favorite_id = Set(Some(self.id));
|
video_model.favorite_id = Set(Some(self.id));
|
||||||
if let Some(fmt_args) = &video_info.to_fmt_args() {
|
helper::video_with_path(video_model, &self.path, video_info)
|
||||||
video_model.path = Set(Path::new(&self.path)
|
|
||||||
.join(filenamify(
|
|
||||||
TEMPLATE
|
|
||||||
.render("video", fmt_args)
|
|
||||||
.unwrap_or_else(|_| video_info.bvid().to_string()),
|
|
||||||
))
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string());
|
|
||||||
}
|
|
||||||
video_model
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_videos_detail(
|
async fn fetch_videos_detail(
|
||||||
@@ -143,7 +77,7 @@ impl VideoListModel for favorite::Model {
|
|||||||
Ok((tags, pages_info)) => {
|
Ok((tags, pages_info)) => {
|
||||||
let txn = connection.begin().await?;
|
let txn = connection.begin().await?;
|
||||||
// 将分页信息写入数据库
|
// 将分页信息写入数据库
|
||||||
create_video_pages(&pages_info, &video_model, &txn).await?;
|
helper::create_video_pages(&pages_info, &video_model, &txn).await?;
|
||||||
// 将页标记和 tag 写入数据库
|
// 将页标记和 tag 写入数据库
|
||||||
let mut video_active_model: video::ActiveModel = video_model.into();
|
let mut video_active_model: video::ActiveModel = video_model.into();
|
||||||
video_active_model.single_page = Set(Some(pages_info.len() == 1));
|
video_active_model.single_page = Set(Some(pages_info.len() == 1));
|
||||||
@@ -152,7 +86,7 @@ impl VideoListModel for favorite::Model {
|
|||||||
txn.commit().await?;
|
txn.commit().await?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error_fetch_video_detail(e, video_model, connection).await?;
|
helper::error_fetch_video_detail(e, video_model, connection).await?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -185,3 +119,36 @@ impl VideoListModel for favorite::Model {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn favorite_from<'a>(
|
||||||
|
fid: &str,
|
||||||
|
path: &Path,
|
||||||
|
bili_client: &'a BiliClient,
|
||||||
|
connection: &DatabaseConnection,
|
||||||
|
) -> Result<(Box<dyn VideoListModel>, Pin<Box<dyn Stream<Item = VideoInfo> + 'a>>)> {
|
||||||
|
let favorite = FavoriteList::new(bili_client, fid.to_owned());
|
||||||
|
let favorite_info = favorite.get_info().await?;
|
||||||
|
favorite::Entity::insert(favorite::ActiveModel {
|
||||||
|
f_id: Set(favorite_info.id),
|
||||||
|
name: Set(favorite_info.title.clone()),
|
||||||
|
path: Set(path.to_string_lossy().to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.on_conflict(
|
||||||
|
OnConflict::column(favorite::Column::FId)
|
||||||
|
.update_columns([favorite::Column::Name, favorite::Column::Path])
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.exec(connection)
|
||||||
|
.await?;
|
||||||
|
Ok((
|
||||||
|
Box::new(
|
||||||
|
favorite::Entity::find()
|
||||||
|
.filter(favorite::Column::FId.eq(favorite_info.id))
|
||||||
|
.one(connection)
|
||||||
|
.await?
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
Box::pin(favorite.into_video_stream()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
136
crates/bili_sync/src/adapter/helper/mod.rs
Normal file
136
crates/bili_sync/src/adapter/helper/mod.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use bili_sync_entity::*;
|
||||||
|
use filenamify::filenamify;
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use sea_orm::sea_query::OnConflict;
|
||||||
|
use sea_orm::ActiveValue::Set;
|
||||||
|
use sea_orm::{Condition, QuerySelect};
|
||||||
|
|
||||||
|
use crate::bilibili::{BiliError, PageInfo, VideoInfo};
|
||||||
|
use crate::config::TEMPLATE;
|
||||||
|
use crate::utils::id_time_key;
|
||||||
|
|
||||||
|
/// 使用 condition 筛选视频,返回视频数量
|
||||||
|
pub(super) async fn count_videos(condition: Condition, conn: &DatabaseConnection) -> Result<u64> {
|
||||||
|
Ok(video::Entity::find().filter(condition).count(conn).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 使用 condition 筛选视频,返回视频列表
|
||||||
|
pub(super) async fn filter_videos(condition: Condition, conn: &DatabaseConnection) -> Result<Vec<video::Model>> {
|
||||||
|
Ok(video::Entity::find().filter(condition).all(conn).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 使用 condition 筛选视频,返回视频列表和相关的分 P 列表
|
||||||
|
pub(super) async fn filter_videos_with_pages(
|
||||||
|
condition: Condition,
|
||||||
|
conn: &DatabaseConnection,
|
||||||
|
) -> Result<Vec<(video::Model, Vec<page::Model>)>> {
|
||||||
|
Ok(video::Entity::find()
|
||||||
|
.filter(condition)
|
||||||
|
.find_with_related(page::Entity)
|
||||||
|
.all(conn)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 返回 videos_info 存在于视频表里那部分对应的 key
|
||||||
|
pub(super) async fn video_keys(
|
||||||
|
videos_info: &[VideoInfo],
|
||||||
|
columns: [video::Column; 2],
|
||||||
|
conn: &DatabaseConnection,
|
||||||
|
) -> Result<HashSet<String>> {
|
||||||
|
Ok(video::Entity::find()
|
||||||
|
.filter(video::Column::Bvid.is_in(videos_info.iter().map(|v| v.bvid().to_string())))
|
||||||
|
.select_only()
|
||||||
|
.columns(columns)
|
||||||
|
.into_tuple()
|
||||||
|
.all(conn)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|(bvid, time)| id_time_key(&bvid, &time))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 返回设置了 path 的视频
|
||||||
|
pub(super) fn video_with_path(
|
||||||
|
mut video_model: video::ActiveModel,
|
||||||
|
base_path: &str,
|
||||||
|
video_info: &VideoInfo,
|
||||||
|
) -> video::ActiveModel {
|
||||||
|
if let Some(fmt_args) = &video_info.to_fmt_args() {
|
||||||
|
video_model.path = Set(Path::new(base_path)
|
||||||
|
.join(filenamify(
|
||||||
|
TEMPLATE
|
||||||
|
.render("video", fmt_args)
|
||||||
|
.unwrap_or_else(|_| video_info.bvid().to_string()),
|
||||||
|
))
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string());
|
||||||
|
}
|
||||||
|
video_model
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 处理获取视频详细信息失败的情况
|
||||||
|
pub(super) async fn error_fetch_video_detail(
|
||||||
|
e: anyhow::Error,
|
||||||
|
video_model: bili_sync_entity::video::Model,
|
||||||
|
connection: &DatabaseConnection,
|
||||||
|
) -> Result<()> {
|
||||||
|
error!(
|
||||||
|
"获取视频 {} - {} 的详细信息失败,错误为:{}",
|
||||||
|
&video_model.bvid, &video_model.name, e
|
||||||
|
);
|
||||||
|
if let Some(BiliError::RequestFailed(-404, _)) = e.downcast_ref::<BiliError>() {
|
||||||
|
let mut video_active_model: bili_sync_entity::video::ActiveModel = video_model.into();
|
||||||
|
video_active_model.valid = Set(false);
|
||||||
|
video_active_model.save(connection).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建视频的所有分 P
|
||||||
|
pub(crate) async fn create_video_pages(
|
||||||
|
pages_info: &[PageInfo],
|
||||||
|
video_model: &video::Model,
|
||||||
|
connection: &impl ConnectionTrait,
|
||||||
|
) -> Result<()> {
|
||||||
|
let page_models = pages_info
|
||||||
|
.iter()
|
||||||
|
.map(move |p| {
|
||||||
|
let (width, height) = match &p.dimension {
|
||||||
|
Some(d) => {
|
||||||
|
if d.rotate == 0 {
|
||||||
|
(Some(d.width), Some(d.height))
|
||||||
|
} else {
|
||||||
|
(Some(d.height), Some(d.width))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => (None, None),
|
||||||
|
};
|
||||||
|
page::ActiveModel {
|
||||||
|
video_id: Set(video_model.id),
|
||||||
|
cid: Set(p.cid),
|
||||||
|
pid: Set(p.page),
|
||||||
|
name: Set(p.name.clone()),
|
||||||
|
width: Set(width),
|
||||||
|
height: Set(height),
|
||||||
|
duration: Set(p.duration),
|
||||||
|
image: Set(p.first_frame.clone()),
|
||||||
|
download_status: Set(0),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<page::ActiveModel>>();
|
||||||
|
page::Entity::insert_many(page_models)
|
||||||
|
.on_conflict(
|
||||||
|
OnConflict::columns([page::Column::VideoId, page::Column::Pid])
|
||||||
|
.do_nothing()
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.do_nothing()
|
||||||
|
.exec(connection)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
mod collection;
|
mod collection;
|
||||||
mod favorite;
|
mod favorite;
|
||||||
|
mod helper;
|
||||||
mod watch_later;
|
mod watch_later;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -8,15 +9,14 @@ use std::pin::Pin;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
pub use collection::collection_from;
|
use collection::collection_from;
|
||||||
pub use favorite::favorite_from;
|
use favorite::favorite_from;
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use sea_orm::ActiveValue::Set;
|
|
||||||
use sea_orm::DatabaseConnection;
|
use sea_orm::DatabaseConnection;
|
||||||
use watch_later::watch_later_from;
|
use watch_later::watch_later_from;
|
||||||
|
|
||||||
use crate::bilibili::{self, BiliClient, BiliError, CollectionItem, VideoInfo};
|
use crate::bilibili::{self, BiliClient, CollectionItem, VideoInfo};
|
||||||
|
|
||||||
pub enum Args<'a> {
|
pub enum Args<'a> {
|
||||||
Favorite { fid: &'a str },
|
Favorite { fid: &'a str },
|
||||||
@@ -37,51 +37,32 @@ pub async fn video_list_from<'a>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn error_fetch_video_detail(
|
|
||||||
e: anyhow::Error,
|
|
||||||
video_model: bili_sync_entity::video::Model,
|
|
||||||
connection: &DatabaseConnection,
|
|
||||||
) -> Result<()> {
|
|
||||||
error!(
|
|
||||||
"获取视频 {} - {} 的详细信息失败,错误为:{}",
|
|
||||||
&video_model.bvid, &video_model.name, e
|
|
||||||
);
|
|
||||||
if let Some(BiliError::RequestFailed(-404, _)) = e.downcast_ref::<BiliError>() {
|
|
||||||
let mut video_active_model: bili_sync_entity::video::ActiveModel = video_model.into();
|
|
||||||
video_active_model.valid = Set(false);
|
|
||||||
video_active_model.save(connection).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait VideoListModel {
|
pub trait VideoListModel {
|
||||||
/* 逻辑相关 */
|
/// 与视频列表关联的视频总数
|
||||||
|
|
||||||
/// 获取与视频列表关联的视频总数
|
|
||||||
async fn video_count(&self, connection: &DatabaseConnection) -> Result<u64>;
|
async fn video_count(&self, connection: &DatabaseConnection) -> Result<u64>;
|
||||||
|
|
||||||
/// 获取未填充的视频
|
/// 未填充的视频
|
||||||
async fn unfilled_videos(&self, connection: &DatabaseConnection) -> Result<Vec<bili_sync_entity::video::Model>>;
|
async fn unfilled_videos(&self, connection: &DatabaseConnection) -> Result<Vec<bili_sync_entity::video::Model>>;
|
||||||
|
|
||||||
/// 获取未处理的视频和分页
|
/// 未处理的视频和分页
|
||||||
async fn unhandled_video_pages(
|
async fn unhandled_video_pages(
|
||||||
&self,
|
&self,
|
||||||
connection: &DatabaseConnection,
|
connection: &DatabaseConnection,
|
||||||
) -> Result<Vec<(bili_sync_entity::video::Model, Vec<bili_sync_entity::page::Model>)>>;
|
) -> Result<Vec<(bili_sync_entity::video::Model, Vec<bili_sync_entity::page::Model>)>>;
|
||||||
|
|
||||||
/// 获取该批次视频的存在标记
|
/// 该批次视频的存在标记
|
||||||
async fn exist_labels(&self, videos_info: &[VideoInfo], connection: &DatabaseConnection)
|
async fn exist_labels(&self, videos_info: &[VideoInfo], connection: &DatabaseConnection)
|
||||||
-> Result<HashSet<String>>;
|
-> Result<HashSet<String>>;
|
||||||
|
|
||||||
/// 获取视频信息对应的视频 model
|
/// 视频信息对应的视频 model
|
||||||
fn video_model_by_info(
|
fn video_model_by_info(
|
||||||
&self,
|
&self,
|
||||||
video_info: &VideoInfo,
|
video_info: &VideoInfo,
|
||||||
base_model: Option<bili_sync_entity::video::Model>,
|
base_model: Option<bili_sync_entity::video::Model>,
|
||||||
) -> bili_sync_entity::video::ActiveModel;
|
) -> bili_sync_entity::video::ActiveModel;
|
||||||
|
|
||||||
/// 获取视频 model 中缺失的信息
|
/// 视频 model 中缺失的信息
|
||||||
async fn fetch_videos_detail(
|
async fn fetch_videos_detail(
|
||||||
&self,
|
&self,
|
||||||
video: bilibili::Video<'_>,
|
video: bilibili::Video<'_>,
|
||||||
@@ -89,16 +70,21 @@ pub trait VideoListModel {
|
|||||||
connection: &DatabaseConnection,
|
connection: &DatabaseConnection,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
/* 日志相关 */
|
/// 开始获取视频
|
||||||
fn log_fetch_video_start(&self);
|
fn log_fetch_video_start(&self);
|
||||||
|
|
||||||
|
/// 结束获取视频
|
||||||
fn log_fetch_video_end(&self);
|
fn log_fetch_video_end(&self);
|
||||||
|
|
||||||
|
/// 开始下载视频
|
||||||
fn log_download_video_start(&self);
|
fn log_download_video_start(&self);
|
||||||
|
|
||||||
|
/// 结束下载视频
|
||||||
fn log_download_video_end(&self);
|
fn log_download_video_end(&self);
|
||||||
|
|
||||||
|
/// 开始刷新视频
|
||||||
fn log_refresh_video_start(&self);
|
fn log_refresh_video_start(&self);
|
||||||
|
|
||||||
|
/// 结束刷新视频
|
||||||
fn log_refresh_video_end(&self, got_count: usize, new_count: u64);
|
fn log_refresh_video_end(&self, got_count: usize, new_count: u64);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,91 +3,54 @@ use std::path::Path;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use async_trait::async_trait;
|
||||||
use bili_sync_entity::*;
|
use bili_sync_entity::*;
|
||||||
use bili_sync_migration::OnConflict;
|
|
||||||
use filenamify::filenamify;
|
|
||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
|
use sea_orm::sea_query::{IntoCondition, OnConflict};
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use sea_orm::{DatabaseConnection, QuerySelect, TransactionTrait};
|
use sea_orm::{DatabaseConnection, TransactionTrait};
|
||||||
|
|
||||||
use crate::adapter::{error_fetch_video_detail, VideoListModel};
|
use crate::adapter::helper::video_with_path;
|
||||||
|
use crate::adapter::{helper, VideoListModel};
|
||||||
use crate::bilibili::{self, BiliClient, VideoInfo, WatchLater};
|
use crate::bilibili::{self, BiliClient, VideoInfo, WatchLater};
|
||||||
use crate::config::TEMPLATE;
|
|
||||||
use crate::utils::id_time_key;
|
|
||||||
use crate::utils::model::create_video_pages;
|
|
||||||
use crate::utils::status::Status;
|
use crate::utils::status::Status;
|
||||||
|
|
||||||
pub async fn watch_later_from<'a>(
|
|
||||||
path: &Path,
|
|
||||||
bili_client: &'a BiliClient,
|
|
||||||
connection: &DatabaseConnection,
|
|
||||||
) -> Result<(Box<dyn VideoListModel>, Pin<Box<dyn Stream<Item = VideoInfo> + 'a>>)> {
|
|
||||||
let watch_later = WatchLater::new(bili_client);
|
|
||||||
watch_later::Entity::insert(watch_later::ActiveModel {
|
|
||||||
id: Set(1),
|
|
||||||
path: Set(path.to_string_lossy().to_string()),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.on_conflict(
|
|
||||||
OnConflict::column(watch_later::Column::Id)
|
|
||||||
.update_column(watch_later::Column::Path)
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.exec(connection)
|
|
||||||
.await?;
|
|
||||||
Ok((
|
|
||||||
Box::new(
|
|
||||||
watch_later::Entity::find()
|
|
||||||
.filter(watch_later::Column::Id.eq(1))
|
|
||||||
.one(connection)
|
|
||||||
.await?
|
|
||||||
.unwrap(),
|
|
||||||
),
|
|
||||||
Box::pin(watch_later.into_video_stream()),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl VideoListModel for watch_later::Model {
|
impl VideoListModel for watch_later::Model {
|
||||||
async fn video_count(&self, connection: &DatabaseConnection) -> Result<u64> {
|
async fn video_count(&self, connection: &DatabaseConnection) -> Result<u64> {
|
||||||
Ok(video::Entity::find()
|
helper::count_videos(video::Column::WatchLaterId.eq(self.id).into_condition(), connection).await
|
||||||
.filter(video::Column::WatchLaterId.eq(self.id))
|
|
||||||
.count(connection)
|
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unfilled_videos(&self, connection: &DatabaseConnection) -> Result<Vec<video::Model>> {
|
async fn unfilled_videos(&self, connection: &DatabaseConnection) -> Result<Vec<video::Model>> {
|
||||||
Ok(video::Entity::find()
|
helper::filter_videos(
|
||||||
.filter(
|
video::Column::WatchLaterId
|
||||||
video::Column::WatchLaterId
|
.eq(self.id)
|
||||||
.eq(self.id)
|
.and(video::Column::Valid.eq(true))
|
||||||
.and(video::Column::Valid.eq(true))
|
.and(video::Column::DownloadStatus.eq(0))
|
||||||
.and(video::Column::DownloadStatus.eq(0))
|
.and(video::Column::Category.eq(2))
|
||||||
.and(video::Column::Category.eq(2))
|
.and(video::Column::SinglePage.is_null())
|
||||||
.and(video::Column::SinglePage.is_null()),
|
.into_condition(),
|
||||||
)
|
connection,
|
||||||
.all(connection)
|
)
|
||||||
.await?)
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn unhandled_video_pages(
|
async fn unhandled_video_pages(
|
||||||
&self,
|
&self,
|
||||||
connection: &DatabaseConnection,
|
connection: &DatabaseConnection,
|
||||||
) -> Result<Vec<(video::Model, Vec<page::Model>)>> {
|
) -> Result<Vec<(video::Model, Vec<page::Model>)>> {
|
||||||
Ok(video::Entity::find()
|
helper::filter_videos_with_pages(
|
||||||
.filter(
|
video::Column::WatchLaterId
|
||||||
video::Column::WatchLaterId
|
.eq(self.id)
|
||||||
.eq(self.id)
|
.and(video::Column::Valid.eq(true))
|
||||||
.and(video::Column::Valid.eq(true))
|
.and(video::Column::DownloadStatus.lt(Status::handled()))
|
||||||
.and(video::Column::DownloadStatus.lt(Status::handled()))
|
.and(video::Column::Category.eq(2))
|
||||||
.and(video::Column::Category.eq(2))
|
.and(video::Column::SinglePage.is_not_null())
|
||||||
.and(video::Column::SinglePage.is_not_null()),
|
.into_condition(),
|
||||||
)
|
connection,
|
||||||
.find_with_related(page::Entity)
|
)
|
||||||
.all(connection)
|
.await
|
||||||
.await?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exist_labels(
|
async fn exist_labels(
|
||||||
@@ -95,37 +58,13 @@ impl VideoListModel for watch_later::Model {
|
|||||||
videos_info: &[VideoInfo],
|
videos_info: &[VideoInfo],
|
||||||
connection: &DatabaseConnection,
|
connection: &DatabaseConnection,
|
||||||
) -> Result<HashSet<String>> {
|
) -> Result<HashSet<String>> {
|
||||||
let bvids = videos_info.iter().map(|v| v.bvid().to_string()).collect::<Vec<_>>();
|
helper::video_keys(videos_info, [video::Column::Bvid, video::Column::Favtime], connection).await
|
||||||
Ok(video::Entity::find()
|
|
||||||
.filter(
|
|
||||||
video::Column::WatchLaterId
|
|
||||||
.eq(self.id)
|
|
||||||
.and(video::Column::Bvid.is_in(bvids)),
|
|
||||||
)
|
|
||||||
.select_only()
|
|
||||||
.columns([video::Column::Bvid, video::Column::Favtime])
|
|
||||||
.into_tuple()
|
|
||||||
.all(connection)
|
|
||||||
.await?
|
|
||||||
.into_iter()
|
|
||||||
.map(|(bvid, time)| id_time_key(&bvid, &time))
|
|
||||||
.collect::<HashSet<_>>())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn video_model_by_info(&self, video_info: &VideoInfo, base_model: Option<video::Model>) -> video::ActiveModel {
|
fn video_model_by_info(&self, video_info: &VideoInfo, base_model: Option<video::Model>) -> video::ActiveModel {
|
||||||
let mut video_model = video_info.to_model(base_model);
|
let mut video_model = video_info.to_model(base_model);
|
||||||
video_model.watch_later_id = Set(Some(self.id));
|
video_model.watch_later_id = Set(Some(self.id));
|
||||||
if let Some(fmt_args) = &video_info.to_fmt_args() {
|
video_with_path(video_model, &self.path, video_info)
|
||||||
video_model.path = Set(Path::new(&self.path)
|
|
||||||
.join(filenamify(
|
|
||||||
TEMPLATE
|
|
||||||
.render("video", fmt_args)
|
|
||||||
.unwrap_or_else(|_| video_info.bvid().to_string()),
|
|
||||||
))
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string());
|
|
||||||
}
|
|
||||||
video_model
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_videos_detail(
|
async fn fetch_videos_detail(
|
||||||
@@ -139,7 +78,7 @@ impl VideoListModel for watch_later::Model {
|
|||||||
Ok((tags, pages_info)) => {
|
Ok((tags, pages_info)) => {
|
||||||
let txn = connection.begin().await?;
|
let txn = connection.begin().await?;
|
||||||
// 将分页信息写入数据库
|
// 将分页信息写入数据库
|
||||||
create_video_pages(&pages_info, &video_model, &txn).await?;
|
helper::create_video_pages(&pages_info, &video_model, &txn).await?;
|
||||||
// 将页标记和 tag 写入数据库
|
// 将页标记和 tag 写入数据库
|
||||||
let mut video_active_model: video::ActiveModel = video_model.into();
|
let mut video_active_model: video::ActiveModel = video_model.into();
|
||||||
video_active_model.single_page = Set(Some(pages_info.len() == 1));
|
video_active_model.single_page = Set(Some(pages_info.len() == 1));
|
||||||
@@ -148,7 +87,7 @@ impl VideoListModel for watch_later::Model {
|
|||||||
txn.commit().await?;
|
txn.commit().await?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error_fetch_video_detail(e, video_model, connection).await?;
|
helper::error_fetch_video_detail(e, video_model, connection).await?;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -181,3 +120,33 @@ impl VideoListModel for watch_later::Model {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn watch_later_from<'a>(
|
||||||
|
path: &Path,
|
||||||
|
bili_client: &'a BiliClient,
|
||||||
|
connection: &DatabaseConnection,
|
||||||
|
) -> Result<(Box<dyn VideoListModel>, Pin<Box<dyn Stream<Item = VideoInfo> + 'a>>)> {
|
||||||
|
let watch_later = WatchLater::new(bili_client);
|
||||||
|
watch_later::Entity::insert(watch_later::ActiveModel {
|
||||||
|
id: Set(1),
|
||||||
|
path: Set(path.to_string_lossy().to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.on_conflict(
|
||||||
|
OnConflict::column(watch_later::Column::Id)
|
||||||
|
.update_column(watch_later::Column::Path)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.exec(connection)
|
||||||
|
.await?;
|
||||||
|
Ok((
|
||||||
|
Box::new(
|
||||||
|
watch_later::Entity::find()
|
||||||
|
.filter(watch_later::Column::Id.eq(1))
|
||||||
|
.one(connection)
|
||||||
|
.await?
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
Box::pin(watch_later.into_video_stream()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use sea_orm::ActiveValue::NotSet;
|
use sea_orm::ActiveValue::{NotSet, Set};
|
||||||
use sea_orm::{IntoActiveModel, Set};
|
use sea_orm::IntoActiveModel;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::bilibili::VideoInfo;
|
use crate::bilibili::VideoInfo;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bili_sync_entity::*;
|
use bili_sync_entity::*;
|
||||||
use bili_sync_migration::OnConflict;
|
|
||||||
use sea_orm::entity::prelude::*;
|
use sea_orm::entity::prelude::*;
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::sea_query::OnConflict;
|
||||||
|
|
||||||
use crate::adapter::VideoListModel;
|
use crate::adapter::VideoListModel;
|
||||||
use crate::bilibili::{PageInfo, VideoInfo};
|
use crate::bilibili::VideoInfo;
|
||||||
|
|
||||||
/// 尝试创建 Video Model,如果发生冲突则忽略
|
/// 尝试创建 Video Model,如果发生冲突则忽略
|
||||||
pub async fn create_videos(
|
pub async fn create_videos(
|
||||||
@@ -26,51 +25,6 @@ pub async fn create_videos(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 创建视频的所有分 P
|
|
||||||
pub async fn create_video_pages(
|
|
||||||
pages_info: &[PageInfo],
|
|
||||||
video_model: &video::Model,
|
|
||||||
connection: &impl ConnectionTrait,
|
|
||||||
) -> Result<()> {
|
|
||||||
let page_models = pages_info
|
|
||||||
.iter()
|
|
||||||
.map(move |p| {
|
|
||||||
let (width, height) = match &p.dimension {
|
|
||||||
Some(d) => {
|
|
||||||
if d.rotate == 0 {
|
|
||||||
(Some(d.width), Some(d.height))
|
|
||||||
} else {
|
|
||||||
(Some(d.height), Some(d.width))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => (None, None),
|
|
||||||
};
|
|
||||||
page::ActiveModel {
|
|
||||||
video_id: Set(video_model.id),
|
|
||||||
cid: Set(p.cid),
|
|
||||||
pid: Set(p.page),
|
|
||||||
name: Set(p.name.clone()),
|
|
||||||
width: Set(width),
|
|
||||||
height: Set(height),
|
|
||||||
duration: Set(p.duration),
|
|
||||||
image: Set(p.first_frame.clone()),
|
|
||||||
download_status: Set(0),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<page::ActiveModel>>();
|
|
||||||
page::Entity::insert_many(page_models)
|
|
||||||
.on_conflict(
|
|
||||||
OnConflict::columns([page::Column::VideoId, page::Column::Pid])
|
|
||||||
.do_nothing()
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.do_nothing()
|
|
||||||
.exec(connection)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 更新视频 model 的下载状态
|
/// 更新视频 model 的下载状态
|
||||||
pub async fn update_videos_model(videos: Vec<video::ActiveModel>, connection: &DatabaseConnection) -> Result<()> {
|
pub async fn update_videos_model(videos: Vec<video::ActiveModel>, connection: &DatabaseConnection) -> Result<()> {
|
||||||
video::Entity::insert_many(videos)
|
video::Entity::insert_many(videos)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use bili_sync_entity::{page, video};
|
use bili_sync_entity::*;
|
||||||
use filenamify::filenamify;
|
use filenamify::filenamify;
|
||||||
use futures::stream::{FuturesOrdered, FuturesUnordered};
|
use futures::stream::{FuturesOrdered, FuturesUnordered};
|
||||||
use futures::{Future, Stream, StreamExt};
|
use futures::{Future, Stream, StreamExt};
|
||||||
|
|||||||
Reference in New Issue
Block a user