mirror of
https://github.com/lanyeeee/bilibili-video-downloader.git
synced 2026-09-09 09:36:46 +08:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
184d1fb1d7 | ||
|
|
1766262d6e | ||
|
|
c78a11113a |
@@ -24,10 +24,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{download_progress::DownloadProgress, download_task::DownloadTask};
|
||||||
download_progress::DownloadProgress, download_task::DownloadTask,
|
|
||||||
download_task_state::DownloadTaskState,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct DownloadManager {
|
pub struct DownloadManager {
|
||||||
pub app: AppHandle,
|
pub app: AppHandle,
|
||||||
@@ -91,7 +88,7 @@ impl DownloadManager {
|
|||||||
let old_task = tasks.insert(new_task.task_id.clone(), new_task);
|
let old_task = tasks.insert(new_task.task_id.clone(), new_task);
|
||||||
if let Some(old_task) = old_task {
|
if let Some(old_task) = old_task {
|
||||||
// 如果同一个ID的下载任务已经存在,则取消旧的任务
|
// 如果同一个ID的下载任务已经存在,则取消旧的任务
|
||||||
let _ = old_task.cancel_sender.send(());
|
old_task.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +117,7 @@ impl DownloadManager {
|
|||||||
tracing::error!(err_title, message);
|
tracing::error!(err_title, message);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
task.set_state(DownloadTaskState::Paused);
|
task.pause();
|
||||||
tracing::debug!("已将ID对应的下载任务状态设置为`Paused`");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,8 +135,7 @@ impl DownloadManager {
|
|||||||
tracing::error!(err_title, message);
|
tracing::error!(err_title, message);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
task.set_state(DownloadTaskState::Pending);
|
task.resume();
|
||||||
tracing::debug!("已将ID对应的下载任务状态设置为`Pending`");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,27 +153,13 @@ impl DownloadManager {
|
|||||||
tracing::error!(err_title, message);
|
tracing::error!(err_title, message);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
// TODO: 应该先发删除新号再删文件
|
if let Err(err) = task.delete() {
|
||||||
// 因为发信号失败会把任务重新塞回去
|
|
||||||
// 目前先删文件会导致发信号失败时出现 任务还在但文件没了的情况
|
|
||||||
if let Err(err) = self.delete_progress_file(task_id) {
|
|
||||||
let err_title = "删除下载任务失败";
|
let err_title = "删除下载任务失败";
|
||||||
let message = err.to_message();
|
let message = err.to_message();
|
||||||
tracing::error!(err_title, message);
|
tracing::error!(err_title, message);
|
||||||
tasks.insert(task_id.clone(), task);
|
tasks.insert(task_id.clone(), task);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = task.delete_sender.send(()).map_err(eyre::Report::from) {
|
|
||||||
let err = err.wrap_err("通知ID对应的下载任务删除失败");
|
|
||||||
let err_title = "删除下载任务失败";
|
|
||||||
let message = err.to_message();
|
|
||||||
tracing::error!(err_title, message);
|
|
||||||
tasks.insert(task_id.clone(), task);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::debug!("已通知ID对应的下载任务删除");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,15 +178,12 @@ impl DownloadManager {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) {
|
if let Err(err) = task.restart() {
|
||||||
let err_title = "重来下载任务失败";
|
let err_title = "重来下载任务失败";
|
||||||
let err = err.wrap_err("通知ID对应的下载任务重来失败");
|
|
||||||
let message = err.to_message();
|
let message = err.to_message();
|
||||||
tracing::error!(err_title, message);
|
tracing::error!(err_title, message);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("已通知ID对应的下载任务重来");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,37 +200,11 @@ impl DownloadManager {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: 把这块代码变成DownloadProgress的mark_restart函数
|
if let Err(err) = task.restart_with_params(params) {
|
||||||
{
|
|
||||||
let mut progress = task.progress.write();
|
|
||||||
|
|
||||||
progress.video_task.selected = params.video_task_selected;
|
|
||||||
progress.audio_task.selected = params.audio_task_selected;
|
|
||||||
progress.video_process_task.merge_selected = params.merge_selected;
|
|
||||||
progress.video_process_task.embed_chapter_selected = params.embed_chapter_selected;
|
|
||||||
progress.video_process_task.embed_skip_selected = params.embed_skip_selected;
|
|
||||||
progress.subtitle_task.selected = params.subtitle_task_selected;
|
|
||||||
progress.danmaku_task.xml_selected = params.xml_danmaku_selected;
|
|
||||||
progress.danmaku_task.ass_selected = params.ass_danmaku_selected;
|
|
||||||
progress.danmaku_task.json_selected = params.json_danmaku_selected;
|
|
||||||
progress.cover_task.selected = params.cover_task_selected;
|
|
||||||
progress.nfo_task.selected = params.nfo_task_selected;
|
|
||||||
progress.json_task.selected = params.json_task_selected;
|
|
||||||
|
|
||||||
progress.video_task.video_quality = params.video_quality;
|
|
||||||
progress.video_task.codec_type = params.codec_type;
|
|
||||||
progress.audio_task.audio_quality = params.audio_quality;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(err) = task.restart_sender.send(()).map_err(eyre::Report::from) {
|
|
||||||
let err_title = "重来下载任务失败";
|
let err_title = "重来下载任务失败";
|
||||||
let err = err.wrap_err("通知ID对应的下载任务重来失败");
|
|
||||||
let message = err.to_message();
|
let message = err.to_message();
|
||||||
tracing::error!(err_title, message);
|
tracing::error!(err_title, message);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::debug!("已通知ID对应的下载任务重来");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn emit_download_speed_loop(app: AppHandle, byte_per_sec: Arc<AtomicU64>) {
|
async fn emit_download_speed_loop(app: AppHandle, byte_per_sec: Arc<AtomicU64>) {
|
||||||
@@ -274,14 +226,4 @@ impl DownloadManager {
|
|||||||
let task_dir = app_data_dir.join(".下载任务");
|
let task_dir = app_data_dir.join(".下载任务");
|
||||||
Ok(task_dir)
|
Ok(task_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(level = "error", skip_all, fields(task_id = task_id))]
|
|
||||||
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() {
|
|
||||||
std::fs::remove_file(task_file)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use eyre::WrapErr;
|
use eyre::WrapErr;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use tauri::AppHandle;
|
use tauri::{AppHandle, Manager};
|
||||||
use tauri_specta::Event;
|
use tauri_specta::Event;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
sync::{SemaphorePermit, watch},
|
sync::{SemaphorePermit, watch},
|
||||||
@@ -14,7 +14,10 @@ use crate::{
|
|||||||
downloader::episode_type::EpisodeType,
|
downloader::episode_type::EpisodeType,
|
||||||
events::DownloadEvent,
|
events::DownloadEvent,
|
||||||
extensions::{AppHandleExt, EyreReportToMessage},
|
extensions::{AppHandleExt, EyreReportToMessage},
|
||||||
types::create_download_task_params::CreateDownloadTaskParams,
|
types::{
|
||||||
|
create_download_task_params::CreateDownloadTaskParams,
|
||||||
|
restart_download_task_params::RestartDownloadTaskParams,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{download_progress::DownloadProgress, download_task_state::DownloadTaskState};
|
use super::{download_progress::DownloadProgress, download_task_state::DownloadTaskState};
|
||||||
@@ -199,6 +202,70 @@ impl DownloadTask {
|
|||||||
task
|
task
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "error", skip_all, fields(task_id = self.task_id))]
|
||||||
|
pub fn pause(&self) {
|
||||||
|
self.set_state(DownloadTaskState::Paused);
|
||||||
|
tracing::debug!("已将下载任务状态设置为`Paused`");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "error", skip_all, fields(task_id = self.task_id))]
|
||||||
|
pub fn resume(&self) {
|
||||||
|
self.set_state(DownloadTaskState::Pending);
|
||||||
|
tracing::debug!("已将下载任务状态设置为`Pending`");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "error", skip_all, fields(task_id = self.task_id))]
|
||||||
|
pub fn cancel(&self) {
|
||||||
|
let _ = self.cancel_sender.send(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "error", skip_all, fields(task_id = self.task_id))]
|
||||||
|
pub fn delete(&self) -> eyre::Result<()> {
|
||||||
|
self.delete_progress_file()?;
|
||||||
|
self.delete_sender
|
||||||
|
.send(())
|
||||||
|
.map_err(eyre::Report::from)
|
||||||
|
.wrap_err("通知ID对应的下载任务删除失败")?;
|
||||||
|
tracing::debug!("已通知ID对应的下载任务删除");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "error", skip_all, fields(task_id = self.task_id))]
|
||||||
|
pub fn restart(&self) -> eyre::Result<()> {
|
||||||
|
self.restart_sender
|
||||||
|
.send(())
|
||||||
|
.map_err(eyre::Report::from)
|
||||||
|
.wrap_err("通知ID对应的下载任务重来失败")?;
|
||||||
|
tracing::debug!("已通知ID对应的下载任务重来");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "error", skip_all, fields(task_id = self.task_id))]
|
||||||
|
pub fn restart_with_params(&self, params: &RestartDownloadTaskParams) -> eyre::Result<()> {
|
||||||
|
{
|
||||||
|
let mut progress = self.progress.write();
|
||||||
|
|
||||||
|
progress.video_task.selected = params.video_task_selected;
|
||||||
|
progress.audio_task.selected = params.audio_task_selected;
|
||||||
|
progress.video_process_task.merge_selected = params.merge_selected;
|
||||||
|
progress.video_process_task.embed_chapter_selected = params.embed_chapter_selected;
|
||||||
|
progress.video_process_task.embed_skip_selected = params.embed_skip_selected;
|
||||||
|
progress.subtitle_task.selected = params.subtitle_task_selected;
|
||||||
|
progress.danmaku_task.xml_selected = params.xml_danmaku_selected;
|
||||||
|
progress.danmaku_task.ass_selected = params.ass_danmaku_selected;
|
||||||
|
progress.danmaku_task.json_selected = params.json_danmaku_selected;
|
||||||
|
progress.cover_task.selected = params.cover_task_selected;
|
||||||
|
progress.nfo_task.selected = params.nfo_task_selected;
|
||||||
|
progress.json_task.selected = params.json_task_selected;
|
||||||
|
|
||||||
|
progress.video_task.video_quality = params.video_quality;
|
||||||
|
progress.video_task.codec_type = params.codec_type;
|
||||||
|
progress.audio_task.audio_quality = params.audio_quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.restart()
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(
|
#[instrument(
|
||||||
level = "error",
|
level = "error",
|
||||||
skip_all,
|
skip_all,
|
||||||
@@ -424,7 +491,7 @@ impl DownloadTask {
|
|||||||
up_uid = self.trace_fields.up_uid,
|
up_uid = self.trace_fields.up_uid,
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub fn set_state(&self, state: DownloadTaskState) {
|
fn set_state(&self, state: DownloadTaskState) {
|
||||||
if let Err(err) = self.state_sender.send(state).map_err(eyre::Report::from) {
|
if let Err(err) = self.state_sender.send(state).map_err(eyre::Report::from) {
|
||||||
let err_title = format!("发送状态`{state:?}`失败");
|
let err_title = format!("发送状态`{state:?}`失败");
|
||||||
let message = err.to_message();
|
let message = err.to_message();
|
||||||
@@ -456,6 +523,18 @@ impl DownloadTask {
|
|||||||
tracing::error!(err_title, message);
|
tracing::error!(err_title, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument(level = "error", skip_all, fields(task_id = self.task_id))]
|
||||||
|
fn delete_progress_file(&self) -> eyre::Result<()> {
|
||||||
|
let app_data_dir = self.app.path().app_data_dir()?;
|
||||||
|
let task_file = app_data_dir
|
||||||
|
.join(".下载任务")
|
||||||
|
.join(format!("{}.json", self.task_id));
|
||||||
|
if task_file.exists() {
|
||||||
|
std::fs::remove_file(task_file)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DownloadTaskTraceFields {
|
pub struct DownloadTaskTraceFields {
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
pub mod audio_quality;
|
pub mod audio_quality;
|
||||||
pub mod available_media_formats;
|
pub mod available_media_formats;
|
||||||
pub mod bangumi_follow_info;
|
pub mod bangumi_follow_info;
|
||||||
@@ -36,3 +38,12 @@ pub mod user_info;
|
|||||||
pub mod user_video_info;
|
pub mod user_video_info;
|
||||||
pub mod video_quality;
|
pub mod video_quality;
|
||||||
pub mod watch_later_info;
|
pub mod watch_later_info;
|
||||||
|
|
||||||
|
fn deserialize_null_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
|
||||||
|
where
|
||||||
|
T: Default + Deserialize<'de>,
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let opt = Option::deserialize(deserializer)?;
|
||||||
|
Ok(opt.unwrap_or_default())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use specta::Type;
|
||||||
|
|
||||||
|
use super::deserialize_null_default;
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
|
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
@@ -30,6 +32,7 @@ pub struct PlayerInfo {
|
|||||||
pub online_count: i64,
|
pub online_count: i64,
|
||||||
pub need_login_subtitle: bool,
|
pub need_login_subtitle: bool,
|
||||||
pub subtitle: SubtitleInPlayerInfo,
|
pub subtitle: SubtitleInPlayerInfo,
|
||||||
|
#[serde(default, deserialize_with = "deserialize_null_default")]
|
||||||
pub view_points: Vec<ViewPoint>,
|
pub view_points: Vec<ViewPoint>,
|
||||||
pub preview_toast: String,
|
pub preview_toast: String,
|
||||||
pub options: Options,
|
pub options: Options,
|
||||||
|
|||||||
+11
-19
@@ -27,36 +27,28 @@ export const useStore = defineStore('store', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function useProgresses() {
|
function useProgresses() {
|
||||||
// 内部的高频更新状态
|
|
||||||
const _progresses = new Map<string, ProgressData>()
|
|
||||||
// 对外暴露的响应式状态
|
// 对外暴露的响应式状态
|
||||||
const progresses = ref<Map<string, ProgressData>>(new Map())
|
const progresses = ref<Map<string, ProgressData>>(new Map())
|
||||||
|
|
||||||
|
// 等待在同一渲染帧内执行的更新函数
|
||||||
|
const pendingUpdateFns: Array<(progresses: Map<string, ProgressData>) => void> = []
|
||||||
|
|
||||||
// 用于确保在同一渲染帧内只安排一次UI更新
|
// 用于确保在同一渲染帧内只安排一次UI更新
|
||||||
let isUpdateScheduled = false
|
let isUpdateScheduled = false
|
||||||
|
|
||||||
// 将 `_progresses` 的内容更新到 `progresses` 中,并触发重新渲染
|
// 在同一渲染帧内集中执行等待中的更新函数
|
||||||
const updateProgressesOnFrame = () => {
|
const updateProgressesOnFrame = () => {
|
||||||
const newProgressesMap = new Map<string, ProgressData>()
|
|
||||||
|
|
||||||
for (const [key, value] of _progresses.entries()) {
|
|
||||||
const progressData = progresses.value.get(key)
|
|
||||||
|
|
||||||
if (progressData !== undefined) {
|
|
||||||
Object.assign(progressData, value)
|
|
||||||
newProgressesMap.set(key, progressData)
|
|
||||||
} else {
|
|
||||||
newProgressesMap.set(key, { ...value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
progresses.value = newProgressesMap
|
|
||||||
|
|
||||||
isUpdateScheduled = false
|
isUpdateScheduled = false
|
||||||
|
|
||||||
|
const updateFns = pendingUpdateFns.splice(0)
|
||||||
|
for (const updateFn of updateFns) {
|
||||||
|
updateFn(progresses.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateProgresses = (updateFn: (progresses: Map<string, ProgressData>) => void) => {
|
const updateProgresses = (updateFn: (progresses: Map<string, ProgressData>) => void) => {
|
||||||
// 使用传入的更新函数来修改 `_progresses`
|
// 将传入的更新函数添加到等待队列
|
||||||
updateFn(_progresses)
|
pendingUpdateFns.push(updateFn)
|
||||||
|
|
||||||
if (!isUpdateScheduled) {
|
if (!isUpdateScheduled) {
|
||||||
// 如果没有安排过UI更新,则安排一次
|
// 如果没有安排过UI更新,则安排一次
|
||||||
|
|||||||
Reference in New Issue
Block a user