简介
Vielpork是一个高性能的多线程HTTP下载器,由Rust编写,具有可自定义的报告器和资源解析策略。它提供:
- 🚀 多线程下载以获得最大速度
- 📊 多种内置报告器适配大部分场景
- 📦 丰富的路径策略选项与模板命名支持
- 🔧 为不同下载场景提供可定制的资源解析策略
- ⏯️ 支持全局与单个任务的暂停/恢复功能
stateDiagram-v2 [*] --> GlobalInit GlobalInit --> GlobalRunning: start_all() GlobalRunning --> GlobalSuspended: pause_all() GlobalSuspended --> GlobalRunning: resume_all() GlobalRunning --> GlobalStopped: cancel_all() GlobalStopped --> [*] state TaskStates { [*] --> TaskPending TaskPending --> TaskDownloading: start_task() TaskDownloading --> TaskPaused: pause_task() TaskPaused --> TaskDownloading: resume_task() TaskDownloading --> TaskCanceled: cancel_task() TaskDownloading --> TaskCompleted: finish() TaskPaused --> TaskCanceled: cancel_task() TaskCanceled --> [*] TaskCompleted --> [*] } GlobalSuspended --> TaskPaused : propagate GlobalStopped --> TaskCanceled : propagate
相关项目
- osynic_downloader: 基于vielpork的osu!谱面下载器,包含工具库和TUI应用
核心特性
- 多线程架构:利用Rust的异步运行时进行并发的分块下载
- 可扩展的报告系统:
- 内置报告器:TUI进度条,CLI广播mpsc通道
- 通过trait实现自定义报告器
- 智能解析:
- 通过Resolver trait进行自定义解析逻辑
- 恢复与韧性:
- 继续上次中断的下载
- 进度跟踪:
- 实时速度计算
- ETA估算
- 详细的传输统计
内置选项
报告器
- TuiReporter:基于
indicatif
库的终端进度条 - CliReporterBoardcastMpsc:一个广播进度更新到多个通道并用单个通道完成的报告器(使用示例:在Tonic gRPC服务器流中,rx类型只能是mpsc,因此我们需要将进度广播到mpsc通道,然后通过服务器将其发送到客户端)
解析器
- UrlResolver:一个从URL下载资源的解析器,只是reqwest的简单包装
自定义组件
您可以在vielpork::base::traits
中查看所有trait并实现自己的组件。
自定义报告器
- 这里有2个需要使用async_trait实现的trait:
ProgressReporter
:允许报告器处理进度更新的traitResultReporter
:允许报告器处理操作或任务的结果的trait
自定义解析器
- 这里只有1个需要使用async_trait实现的trait:
ResourceResolver
:允许解析器从特定来源下载资源的trait
后记(或者说最开始的序章)
最开始找到了viel这个词,后面想了下rufen、ekstase、reichen
但是正在我还在犹豫不决的时候,好朋友来寝室送了我一纸杯的熏猪肉丝
所以我就直接取名叫做vielpork了,这个名字的意思是很多猪肉丝
但如果是功能描述的话,这个下载器主打的是多报道通道下载,所以也是很多报道
report的vielpork很接近,也还不错
对于连续吃了一个星期免费粥的我来说,这个名字已经很好了
哦对了,水煮肉片也可以算是VielPork了
快速开始
主要分为如下几个部分
- 初始例程
- 流程控制
- 使用实例:OsynicDownloader
在阅读完初始例程后,您可以尝试运行示例代码,下载您需要的资源。
然后,您可以尝试自定义Reporter和Resolver。
- 自定义Reporter
- 自定义Resolver
初始例程
安装
在你的Cargo.toml
中添加:
[dependencies]
vielpork = "0.1.0"
快速开始
use vielpork::downloader::Downloader; use vielpork::reporters::tui::TuiReporter; use vielpork::resolvers::url::UrlResolver; use vielpork::base::structs::DownloadOptions; use vielpork::base::enums::DownloadResource; use vielpork::error::Result; use std::sync::Arc; use tokio::sync::Mutex; #[tokio::main] async fn main() -> Result<()> { let options: DownloadOptions = DownloadOptions::default() .with_save_path("fetch".to_string()) .with_concurrency(3); let downloader = Downloader::new(options, Box::new(UrlResolver::new()), Box::new(TuiReporter::new())); let resources = vec![ DownloadResource::Url("https://example.com".to_string()), DownloadResource::Url("https://example.com".to_string()), DownloadResource::Url("https://example.com".to_string()), DownloadResource::Url("https://example.com".to_string()), DownloadResource::Url("https://example.com".to_string()), DownloadResource::Url("https://example.com".to_string()), DownloadResource::Url("https://example.com".to_string()), DownloadResource::Url("https://example.com".to_string()), DownloadResource::Url("https://example.com".to_string()), ]; downloader.start(resources).await?; loop { tokio::time::sleep(std::time::Duration::from_secs(1)).await; // Because of the async nature of the downloader, we need to keep the main thread alive } Ok(()) }
流程控制
vielpork
提供了完整的流程控制方法,以便于您在编写异步代码时更加方便地控制流程。
-
全局控制
downloader.lock().await.start(resources).await?;
downloader.lock().await.pause().await?;
downloader.lock().await.resume().await?;
downloader.lock().await.cancel().await?;
-
任务控制
downloader.lock().await.pause_task(resource).await?;
downloader.lock().await.resume_task(resource).await?;
downloader.lock().await.cancel_task(resource).await?;
直接在代码中使用即可,vielpork
会自动处理任务的状态转换。
状态转移图
stateDiagram-v2 [*] --> GlobalInit GlobalInit --> GlobalRunning: start_all() GlobalRunning --> GlobalSuspended: pause_all() GlobalSuspended --> GlobalRunning: resume_all() GlobalRunning --> GlobalStopped: cancel_all() GlobalStopped --> [*] state TaskStates { [*] --> TaskPending TaskPending --> TaskDownloading: start_task() TaskDownloading --> TaskPaused: pause_task() TaskPaused --> TaskDownloading: resume_task() TaskDownloading --> TaskCanceled: cancel_task() TaskDownloading --> TaskCompleted: finish() TaskPaused --> TaskCanceled: cancel_task() TaskCanceled --> [*] TaskCompleted --> [*] } GlobalSuspended --> TaskPaused : propagate GlobalStopped --> TaskCanceled : propagate
合理的状态转换
vielpork
的状态转换是有限状态机的形式,每个任务都有一个状态,每个任务的状态转换都是有限的。这样可以保证任务的状态转换是合理的,不会出现不合理的状态转换。
合理全局状态转换
#![allow(unused)] fn main() { let valid = match (*current, new_state) { (DownloaderState::Idle, DownloaderState::Idle) => true, (DownloaderState::Idle, DownloaderState::Running) => true, (DownloaderState::Running, DownloaderState::Suspended) => true, (DownloaderState::Suspended, DownloaderState::Running) => true, (DownloaderState::Stopped, DownloaderState::Idle) => true, (_, DownloaderState::Stopped) => true, _ => false, }; }
合理任务状态转换
#![allow(unused)] fn main() { let valid = match (*current, new_state) { (TaskState::Paused, TaskState::Downloading) => true, (TaskState::Paused, TaskState::Paused) => true, (TaskState::Paused, TaskState::Pending) => true, (TaskState::Pending, TaskState::Paused) => true, (TaskState::Pending, TaskState::Downloading) => true, (TaskState::Downloading, TaskState::Paused) => true, (TaskState::Downloading, TaskState::Completed) => true, (TaskState::Failed, _) => true, (_, TaskState::Failed) => true, (_, TaskState::Canceled) => true, _ => false, }; }
使用实例:OsynicDownloader
osynic_downloader 是一款高效的osu!谱面下载工具,基于vielpork开发,支持两种输入格式和并行下载,专为音游玩家和多设备谱面同步打造。
推荐搭配osynic_serializer使用,实现osu!谱面的快速序列化。
✨ 特性
- 双模式输入:支持osu!谱面集ID列表和Osynic序列化生成的JSON格式
- 多下载源:目前支持OsuDirect、OsuApiV2、SayoApi和ChimuApi共四种下载源
- 并发支持:多线程并发下载加速(默认4线程)(请注意各osu!镜像站API的并发限制!文明使用!)
- 智能管理:自动创建目录结构,自定义保存路径
- 可视化进度:实时TUI进度显示(支持终端256色)
- 错误恢复:无畏中断,自动断点续传
📦 安装
预编译版本
cargo install osynic_downloader
源码编译
git clone https://github.com/osynicite/osynic_downloader
cd osynic_downloader
cargo build --release
🚀 快速开始
基本使用
# 原生模式(ID列表)
osynic-dl --beatmapsets json/sets.json -o ./osu_maps -c 8
# Osynic模式(歌曲元数据)
osynic-dl --osynic-songs json/songs.json --output ./music
输入JSON示例
sets.json(Beatmapsets模式):
{
"beatmapset_ids": ["114514", "1919810", "1538879"]
}
songs.json(Songs模式)(Osynic):
[
{
"song_id": 1985060,
"artist_name": "ヒトリエ",
"mapper_name": "flake",
"song_name": "日常と地球の額縁 (wowaka x 初音ミク Edit)",
"no_video": false
},
{
"song_id": 1997071,
"artist_name": "ナブナ",
"mapper_name": "Ryuusei Aika",
"song_name": "始発とカフカ",
"no_video": false
}
]
⚙️ 参数详解
参数 | 简写 | 默认值 | 说明 |
---|---|---|---|
--beatmapsets | -b | - | 原生模式JSON文件路径 |
--osynic-songs | -n | - | Osynic模式JSON文件路径 |
--source | -s | SayoApi | osu!谱面下载源 |
--username | -u | - | osu!账号(仅OsuDirect/OsuApiV2需要) |
--password | -p | - | osu!密码(仅OsuDirect/OsuApiV2需要) |
--output | -o | beatmapsets | 下载目录(自动创建) |
--concurrency | -c | 4 | 下载并发数(1-16) |
--help | -h | - | 显示帮助信息 |
支持的osu!下载源
- OsuDirect:osu!官方谱面下载源(需osu账号密码,做URL传参)
- OsuApiV2: osu!lazer的谱面下载源(需osu账号密码,做Basic认证)
- SayoApi(默认):Sayobot谱面下载源(无需登录)
- ChimuApi:Chimu.moe谱面下载源(无需登录)
📌 注意事项
- 视频下载适配(no_video)暂未实现,相关选项会被忽略
- 下载文件命名遵循
{{filename}}
命名规则 - 使用
Ctrl+C
中断下载进程后,可重新运行恢复下载 - 建议使用稳定的网络连接以获得最佳体验
🤝 贡献指南
欢迎通过Issue提交建议或Pull Request参与开发!请确保:
- 遵循Rust官方编码规范
- 新增功能需附带测试用例
- 提交前运行
cargo fmt
和cargo clippy
📜 开源协议
本项目基于 MIT License 开源,请尊重原作者的著作权。使用osu!相关资源时请遵守osu!社区准则。
自定义Reporter
在vielpork
中,Reporter是一个用于向外部报告下载任务进度和结果的接口。Reporter由两个trait构成,他们定义了一系列的方法,用于向外部报告下载任务的进度和结果。Reporter的实现可以是任何类型,只要它实现了这两个Reporter trait即可。
在vielport::base::traits
中定义的这两个trait是ProgressReporter
和ResultReporter
。ProgressReporter
定义了三个方法,用于报告下载任务的开始、进度更新和结束。ResultReporter
定义了一个方法,用于报告下载任务的结果。
#![allow(unused)] fn main() { #[async_trait] pub trait ProgressReporter { async fn start_task(&self, task_id: u32, total: u64) -> Result<()>; async fn update_progress(&self, task_id: u32, progress: &DownloadProgress) -> Result<()>; async fn finish_task(&self, task_id: u32, result: DownloadResult) -> Result<()>; } #[async_trait] pub trait ResultReporter { async fn operation_result(&self, operation: OperationType, code: u32, message: String) -> Result<()>; } pub trait CombinedReporter: ProgressReporter + ResultReporter + Send + Sync {} impl<T: ProgressReporter + ResultReporter + Send + Sync> CombinedReporter for T {} }
内置使用实例一:TuiReporter
vielpork
提供了一个内置的TUI报告器TuiReporter
,用于在终端中显示下载任务的进度。TuiReporter
实现了ProgressReporter
和ResultReporter
两个trait,用于向外部报告下载任务的进度和结果。
TuiReporter
使用indicatif
库来显示下载任务的进度。在下载任务开始时,TuiReporter
会创建一个新的进度条,并在下载任务结束时更新进度条的状态。TuiReporter
还会在下载任务结束时向外部报告下载任务的结果。
#![allow(unused)] fn main() { use crate::error::Result; use crate::base::structs::DownloadProgress; use crate::base::enums::{DownloadResult, OperationType}; use crate::base::traits::{ProgressReporter, ResultReporter}; use async_trait::async_trait; #[cfg(feature = "tui")] use indicatif::{ProgressBar, ProgressStyle,ProgressDrawTarget,MultiProgress}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex; const MAX_CONCURRENT_BARS: usize = 10 ; // TUI 实现 #[cfg(feature = "tui")] #[derive(Debug)] pub struct TuiReporter { mp: MultiProgress, bars: Arc<Mutex<HashMap<u32, ProgressBar>>>, } impl TuiReporter { pub fn new() -> Self { let mp = Self::setup_global_progress(); Self { mp, bars: Arc::new(Mutex::new(HashMap::new())), } } fn setup_global_progress() -> MultiProgress { let mp = MultiProgress::new(); mp.set_draw_target(ProgressDrawTarget::stdout()); mp } // 私有方法用于获取或创建进度条 async fn get_or_create_bar(&self, task_id: u32, total: u64) -> ProgressBar { let mut bars = self.bars.lock().await; if bars.len() >= MAX_CONCURRENT_BARS { bars.retain(|_, bar| !bar.is_finished()); } bars.entry(task_id) .or_insert_with(|| { let bar = ProgressBar::new(total); // 关键修改:将进度条添加到 MultiProgress 系统 let bar = self.mp.add(bar); // 这行是核心修改 bar.set_style(ProgressStyle::with_template(&format!( "{{spinner:.green}} [{{bar:.cyan/blue}}] {{bytes}}/{{total_bytes}} ({}) {{msg}}", task_id )) .unwrap_or(ProgressStyle::default_bar()) .progress_chars("#>-")); bar }); bars.get(&task_id).unwrap_or(&ProgressBar::hidden()).clone() } } #[async_trait] impl ProgressReporter for TuiReporter { async fn start_task(&self, task_id: u32, total: u64) -> Result<()> { let bar = self.get_or_create_bar(task_id, total).await; bar.set_message("Downloading..."); Ok(()) } async fn update_progress(&self, task_id: u32, progress: &DownloadProgress) -> Result<()> { let bar = self.get_or_create_bar(task_id, progress.total_bytes).await; bar.set_position(progress.bytes_downloaded); let speed = if progress.rate > 1_000_000.0 { format!("{:.2} MB/s", progress.rate / 1_000_000.0) } else if progress.rate > 1_000.0 { format!("{:.2} KB/s", progress.rate / 1_000.0) } else { format!("{:.0} B/s", progress.rate) }; // 格式化剩余时间 let eta = if progress.remaining_time.as_secs() > 60 { format!("{}m {}s", progress.remaining_time.as_secs() / 60, progress.remaining_time.as_secs() % 60) } else { format!("{}s", progress.remaining_time.as_secs()) }; bar.set_message(format!("Speed: {} | ETA: {}", speed,eta)); Ok(()) } async fn finish_task(&self, task_id: u32,result: DownloadResult) -> Result<()> { let mut bars = self.bars.lock().await; if let Some(bar) = bars.remove(&task_id) { match result { DownloadResult::Success { path ,duration ,.. } => { // 条的颜色变成绿色,还是#>- bar.set_style(ProgressStyle::with_template(&format!( "{{spinner:.green}} [{{bar:.green/blue}}] {{bytes}}/{{total_bytes}} ({}): {{msg}}", task_id ))?.progress_chars("#>-")); let success_message = format!("✅ Done in {}s, saved to {}",duration.as_secs(),path.display()); bar.finish_with_message(success_message) }, DownloadResult::Failed { error,.. } => { // 条变成红色 bar.set_style(ProgressStyle::default_bar().template(&format!( "{{spinner:.red}} [{{bar:.red/blue}}] {{bytes}}/{{total_bytes}} ({}): {{msg}}", task_id ))?.progress_chars("#>-")); let error_message = format!("❌ Error: {}", error); bar.abandon_with_message(error_message) }, DownloadResult::Canceled => { // 条变成黄色 bar.set_style(ProgressStyle::default_bar().template(&format!( "{{spinner:.red}} [{{bar:.yellow/blue}}] {{bytes}}/{{total_bytes}} ({}): {{msg}}", task_id ))?.progress_chars("#>-")); bar.abandon_with_message("⛔ Canceled") }, } } Ok(()) } } #[async_trait] impl ResultReporter for TuiReporter { async fn operation_result(&self, operation: OperationType, code: u32, message: String) -> Result<()> { if code == 200 { println!("{}: {}", operation, message); } else { eprintln!("{}: {}", operation, message); } Ok(()) } } }
内置使用实例二:CliReporterBoardcastMpsc
vielpork
提供了一个内置的CLI广播报告器CliReporterBoardcastMpsc
,用于将下载任务的进度广播到多个mpsc通道。CliReporterBoardcastMpsc
实现了ProgressReporter
和ResultReporter
两个trait,用于向外部报告下载任务的进度和结果。
CliReporterBoardcastMpsc
使用tokio::sync::mpsc
来广播下载任务的进度。在下载任务开始时,CliReporterBoardcastMpsc
会向多个mpsc通道发送进度更新消息,并在下载任务结束时向外部报告下载任务的结果。
这个Reporter的实现比较简单,只需要在ProgressReporter
和ResultReporter
的方法中向多个mpsc通道发送消息即可。最开始是用来解决在Tonic gRPC服务器流中,rx类型只能是mpsc,因此我们需要将进度广播到mpsc通道,然后通过服务器将其发送到客户端。
#![allow(unused)] fn main() { use crate::error::Result; use crate::base::traits::{ProgressReporter, ResultReporter}; use crate::base::structs::DownloadProgress; use crate::base::enums::{ProgressEvent, DownloadResult, OperationType}; use async_trait::async_trait; #[derive(Debug,Clone)] pub struct CliReporterBoardcastMpsc{ inner_tx: tokio::sync::broadcast::Sender<ProgressEvent>, buffer_size: usize, } impl CliReporterBoardcastMpsc { pub fn new(buffer_size: usize) -> Self { let (inner_tx, _) = tokio::sync::broadcast::channel(buffer_size); Self { inner_tx, buffer_size } } pub fn subscribe_mpsc(&self) -> tokio::sync::mpsc::Receiver<ProgressEvent> { let (tx, rx) = tokio::sync::mpsc::channel(self.buffer_size); let mut inner_rx = self.inner_tx.subscribe(); tokio::spawn(async move { loop { match inner_rx.recv().await { Ok(event) => { if tx.send(event).await.is_err() { break; } } Err(_) => { tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; } } } }); rx } // 发送事件的方法 pub async fn send(&self, event: ProgressEvent) -> Result<usize> { self.inner_tx.send(event)?; Ok(self.inner_tx.receiver_count()) } // 创建新订阅者 pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver<ProgressEvent> { self.inner_tx.subscribe() } } #[async_trait] impl ProgressReporter for CliReporterBoardcastMpsc { async fn start_task(&self, task_id: u32, total: u64) ->Result<()> { self.send(ProgressEvent::Start { task_id, total }).await?; Ok(()) } async fn update_progress(&self, task_id: u32, progress: &DownloadProgress)->Result<()> { self.send(ProgressEvent::Update { task_id, progress: progress.clone() }).await?; Ok(()) } async fn finish_task(&self, task_id: u32,finish: DownloadResult) ->Result<()>{ self.send(ProgressEvent::Finish { task_id ,finish}).await?; Ok(()) } } #[async_trait] impl ResultReporter for CliReporterBoardcastMpsc { async fn operation_result(&self, operation: OperationType, code: u32, message: String) ->Result<()> { self.send(ProgressEvent::OperationResult { operation, code, message }).await?; Ok(()) } } }
自定义Resolver
在vielpork
中,Resolver
是一个trait,用于解析资源的来源。vielpork
提供了一个内置的UrlResolver
,用于从URL下载资源。您可以通过实现Resolver
trait来自定义解析逻辑。
在vielport::base::traits
中定义的这个trait是ResourceResolver
。ResourceResolver
定义了一个方法,用于解析资源的来源。
#![allow(unused)] fn main() { #[async_trait] pub trait ResourceResolver: Send + Sync { async fn resolve(&self, resource: &DownloadResource) -> Result<ResolvedResource>; } }
Resolver何为?
Resolver
用来将DownloadResource
转换为ResolvedResource
。DownloadResource
是一个枚举类型,它包含了资源的不同类型。ResolvedResource
是一个结构体,它包含了HTTP请求所需的详细信息。
#![allow(unused)] fn main() { // `vielpork::base::enums`中定义的`DownloadResource`枚举类型 #[derive(Debug, Clone, Serialize, Deserialize)] pub enum DownloadResource { Url(String), Id(String), Params(Vec<String>), HashMap(HashMap<String, String>), Resolved(ResolvedResource), } }
#![allow(unused)] fn main() { // `vielpork::base::structs`中定义的`ResolvedResource`结构体 #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResolvedResource { pub id: u32, pub url: String, pub headers: Vec<(String, String)>, pub auth: Option<AuthMethod>, } }
其中,AuthMethod
支持Basic
、Bearer
以及ApiKey
三种认证方式。
#![allow(unused)] fn main() { // `vielpork::base::enums`中定义的`AuthMethod`枚举类型 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum AuthMethod { None, Basic { username: String, password: String }, Bearer { token: String }, ApiKey { key: String, header: String }, } }
内置使用实例一:UrlResolver
UrlResolver
是一个简单的解析器,用于从URL下载资源。
实际上,UrlResolver
只是一个简单的reqwest包装器,它将URL转换为ResolvedResource
。
#![allow(unused)] fn main() { use crate::error::Result; use crate::base::traits::ResourceResolver; use crate::base::structs::ResolvedResource; use crate::base::enums::DownloadResource; use crate::base::algorithms::generate_task_id; use async_trait::async_trait; #[derive(Debug,Clone)] pub struct UrlResolver {} impl UrlResolver { pub fn new() -> Self { Self {} } } #[async_trait] impl ResourceResolver for UrlResolver { async fn resolve(&self, resource: &DownloadResource) -> Result<ResolvedResource> { match resource { DownloadResource::Url(url) => { Ok(ResolvedResource{ id: generate_task_id(url), url: url.clone(), headers: vec![], auth: None, }) } DownloadResource::Resolved(resolved) => { Ok(resolved.clone()) } _ => { Err("Unsupported resource type".into()) } } } } }
外部使用实例一:OsuBeatmapResolver
这一部分可以详见osynic_downloader的源码。
osynic_downloader
是一个基于vielpork
的osu!谱面下载器,包含工具库和TUI应用。
它使用的Resolver是OsuBeatmapResolver
,这个Resolver是一个从osu!官网、API以及各打镜像站下载谱面的资源解析器。
例如:
- 对于Url类型的资源,直接返回Url;
- 对于Id类型的资源,根据Id生成Url,使用默认的下载源;
- 对于Params类型的资源,根据参数生成Url,使用参数指定的下载源;
- 对于HashMap类型的资源,根据HashMap生成Url,使用参数指定的下载源。
#![allow(unused)] fn main() { use vielpork::base::traits::ResourceResolver; use vielpork::base::structs::ResolvedResource; use vielpork::base::enums::{DownloadResource,AuthMethod}; use vielpork::base::algorithms::generate_task_id; use async_trait::async_trait; use crate::sources::{DownloadSource, DownloadSourceType}; use crate::url::form_url; #[derive(Debug,Clone)] pub struct OsuBeatmapsetResolver {} impl OsuBeatmapsetResolver { pub fn new() -> Self { Self {} } } #[async_trait] impl ResourceResolver for OsuBeatmapsetResolver { async fn resolve(&self, resource: &DownloadResource) -> vielpork::error::Result<ResolvedResource> { match resource { DownloadResource::Url(url) => { Ok(ResolvedResource{ id: generate_task_id(url), url: url.clone(), headers: vec![], auth: None, }) } DownloadResource::Id(id) => { let beatmapset_id: u32; match id.parse::<u32>() { Ok(id) => { beatmapset_id = id; } Err(_) => { return Err("Invalid beatmapset id".into()); } } let download_source = DownloadSource::from(DownloadSourceType::Default); let base_url = download_source.base_url.clone(); let url = form_url(&base_url, &beatmapset_id, "", "").map_err(|e| e.to_string())?; Ok(ResolvedResource{ id: beatmapset_id, url: url.clone(), headers: vec![], auth: None, }) } DownloadResource::Params(params) => { let beatmapset_id: u32; let source: String; match params.get(0) { Some(id) => { match id.parse::<u32>() { Ok(id) => { beatmapset_id = id; } Err(_) => { return Err("Invalid beatmapset id".into()); } } } None => { return Err("Missing beatmapset_id".into()); } } match params.get(1) { Some(src) => { source = src.clone(); } None => { return Err("Missing source".into()); } } let download_source = DownloadSource::from(DownloadSourceType::from(source)); let base_url = download_source.base_url.clone(); let username: String; let password: String; let url: String; if download_source.requires_osu_credentials { match params.get(2) { Some(name) => { username = name.clone(); } None => { return Err("Missing username".into()); } } match params.get(3) { Some(pass) => { password = pass.clone(); } None => { return Err("Missing password".into()); } } if download_source.requires_basic_auth{ url = form_url(&base_url, &beatmapset_id, "","").map_err(|e| e.to_string())?; Ok( ResolvedResource{ id:beatmapset_id, url: url.clone(), headers: vec![], auth: Some(AuthMethod::Basic { username, password }), } ) } else { let hashed_password = format!("{:x}", md5::compute(password)); url = form_url(&base_url, &beatmapset_id, &username, &hashed_password).map_err(|e| e.to_string())?; Ok( ResolvedResource{ id:beatmapset_id, url: url.clone(), headers: vec![], auth: None, } ) } } else { url = form_url(&base_url, &beatmapset_id, "", "").map_err(|e| e.to_string())?; Ok( ResolvedResource{ id:beatmapset_id, url: url.clone(), headers: vec![], auth: None, } ) } } DownloadResource::HashMap(hashmap) => { let beatmapset_id: u32; let source: String; match hashmap.get("beatmapset_id") { Some(id) => { match id.parse::<u32>() { Ok(id) => { beatmapset_id = id; } Err(_) => { return Err("Invalid beatmapset id".into()); } } } None => { return Err("Missing beatmapset_id".into()); } } match hashmap.get("source") { Some(src) => { source = src.clone(); } None => { return Err("Missing source".into()); } } let download_source = DownloadSource::from(DownloadSourceType::from(source)); let base_url = download_source.base_url.clone(); let username: String; let password: String; let url: String; if download_source.requires_osu_credentials { match hashmap.get("username") { Some(name) => { username = name.clone(); } None => { return Err("Missing username".into()); } } match hashmap.get("password") { Some(pass) => { password = pass.clone(); } None => { return Err("Missing password".into()); } } if download_source.requires_basic_auth{ url = form_url(&base_url, &beatmapset_id, "","").map_err(|e| e.to_string())?; Ok( ResolvedResource{ id:beatmapset_id, url: url.clone(), headers: vec![], auth: Some(AuthMethod::Basic { username, password }), } ) } else { let hashed_password = format!("{:x}", md5::compute(password)); url = form_url(&base_url, &beatmapset_id, &username, &hashed_password).map_err(|e| e.to_string())?; Ok( ResolvedResource{ id:beatmapset_id, url: url.clone(), headers: vec![], auth: None, } ) } } else { url = form_url(&base_url, &beatmapset_id, "", "").map_err(|e| e.to_string())?; Ok( ResolvedResource{ id:beatmapset_id, url: url.clone(), headers: vec![], auth: None, } ) } } DownloadResource::Resolved(resolved) => { Ok(resolved.clone()) } } } } }
更改日志
0.1
- 从
osynic_core
里面的download
模块抽离出了通用化的HTTP下载器逻辑,并在支持了自定义Reporter和Resolver之后,将其独立为vielpork
库
路线图
暂无更多安排,敬请期待。
附录
状态图
stateDiagram-v2 [*] --> GlobalInit GlobalInit --> GlobalRunning: start_all() GlobalRunning --> GlobalSuspended: pause_all() GlobalSuspended --> GlobalRunning: resume_all() GlobalRunning --> GlobalStopped: cancel_all() GlobalStopped --> [*] state TaskStates { [*] --> TaskPending TaskPending --> TaskDownloading: start_task() TaskDownloading --> TaskPaused: pause_task() TaskPaused --> TaskDownloading: resume_task() TaskDownloading --> TaskCanceled: cancel_task() TaskDownloading --> TaskCompleted: finish() TaskPaused --> TaskCanceled: cancel_task() TaskCanceled --> [*] TaskCompleted --> [*] } GlobalSuspended --> TaskPaused : propagate GlobalStopped --> TaskCanceled : propagate
问题反馈
这个库是差不多一个上午写完的,所以肯定还有很多地方需要改进,目前也只是满足了我自己的项目需求,不能保证完全符合所有人的需求。
所以,如果代码有任何问题,或者你有任何建议,欢迎提交PR或者Issue,我会尽快处理~
如果你想贡献代码,请遵循以下规则:
- 遵循Rust官方编码规范
- 新增功能需附带测试用例
- 提交前运行
cargo fmt
和cargo clippy