增加 file 模块
添加功能函数: abs_path 获取给定路径的绝对路径。 async_download 异步下载文件并保存到指定的目标路径。 download 从给定的 URL 下载文件并将其保存到指定的目标路径。 parent_exists 检查给定路径的父目录是否存在。 Signed-off-by: Jia Chao <jiac13@chinaunicom.cn>
This commit is contained in:
parent
4e2268eb46
commit
3544ad5256
|
@ -11,3 +11,10 @@ description = "A complex toolset that include various small functions."
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
futures-util = { version = "0.3" }
|
||||
indicatif = { version = "0.17" }
|
||||
path-absolutize = { version = "3" }
|
||||
reqwest = { version = "0.12", features = ["stream"] }
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "local-time"] }
|
||||
|
|
197
src/file.rs
Normal file
197
src/file.rs
Normal file
|
@ -0,0 +1,197 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
use tokio::fs::{File, OpenOptions};
|
||||
use tokio::io::{AsyncSeekExt, AsyncWriteExt, SeekFrom};
|
||||
|
||||
use futures_util::StreamExt;
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use path_absolutize::Absolutize;
|
||||
use reqwest::Client;
|
||||
use tracing::{debug, info};
|
||||
|
||||
/// 从给定的 URL 下载文件并将其保存到指定的目标路径。
|
||||
///
|
||||
/// 该函数初始化一个异步运行时,并阻塞在异步下载函数上,直到下载完成。
|
||||
///
|
||||
/// # 参数
|
||||
/// - `url`: 要下载文件的 URL。
|
||||
/// - `target`: 一个可选参数,指定下载文件保存的路径。如果为 `None`,函数会自动确定保存路径。
|
||||
///
|
||||
/// # 返回值
|
||||
/// 该函数返回一个 `Result` 类型,如果下载成功则为 `Ok(())`,如果发生错误则返回错误信息。
|
||||
///
|
||||
/// # 错误
|
||||
/// 如果异步运行时初始化失败或者下载失败,该函数会返回一个错误。
|
||||
///
|
||||
/// # 示例
|
||||
/// ```
|
||||
/// let url = "https://example.com/file.zip";
|
||||
/// let target_path = Some("/path/to/save/file.zip");
|
||||
/// download(url, target_path)?;
|
||||
/// ```
|
||||
///
|
||||
/// # 备注
|
||||
/// `crate::async_runtime` 函数是一个辅助函数,用于返回一个异步运行时的引用。`async_download` 函数是执行实际异步下载过程的实现。
|
||||
pub fn download<P: AsRef<Path>>(url: &str, target: Option<P>) -> crate::Result<()> {
|
||||
// 使用库中的辅助函数初始化异步运行时。
|
||||
let rt = crate::async_runtime()?;
|
||||
// 阻塞在异步下载函数上,直到下载完成。
|
||||
rt.block_on(async_download(url, target))
|
||||
}
|
||||
|
||||
/// 异步下载文件并保存到指定的目标路径。
|
||||
///
|
||||
/// 该函数使用异步 HTTP 客户端从指定的 URL 下载文件,并将其保存到指定的目标路径。
|
||||
/// 如果目标路径为空,则使用 URL 的文件名作为本地文件名。
|
||||
/// 支持断点续传功能。
|
||||
///
|
||||
/// # 参数
|
||||
/// - `url`: 要下载文件的 URL。
|
||||
/// - `target`: 一个可选参数,指定下载文件保存的路径。如果为空,则使用 URL 的文件名。
|
||||
///
|
||||
/// # 返回值
|
||||
/// 该函数返回一个 `Result` 类型,如果下载成功则为 `Ok(())`,如果发生错误则返回错误信息。
|
||||
///
|
||||
/// # 错误
|
||||
/// 如果下载文件的父目录不存在、无法创建或写入文件、或者下载过程中出现任何问题,该函数会返回一个错误。
|
||||
///
|
||||
/// # 示例
|
||||
/// ```rust
|
||||
/// let url = "https://example.com/file.zip";
|
||||
/// let target_path = Some("/path/to/save/file.zip");
|
||||
/// async_download(url, target_path).await?;
|
||||
/// ```
|
||||
///
|
||||
/// # 备注
|
||||
/// 使用 `Client` 进行 HTTP 请求,`OpenOptions` 和 `File` 进行文件操作,支持断点续传。
|
||||
pub async fn async_download<P: AsRef<Path>>(url: &str, target: Option<P>) -> crate::Result<()> {
|
||||
debug!("Download url: {}", url);
|
||||
// 创建一个新的 HTTP 客户端
|
||||
let client = Client::new();
|
||||
// 发送 GET 请求获取响应
|
||||
let response = client.get(url).send().await?;
|
||||
|
||||
// 确定下载文件的路径
|
||||
let download_file = match target {
|
||||
Some(file) => {
|
||||
let file = abs_path(Path::new(file.as_ref()))?;
|
||||
// 检查下载文件的父目录是否存在
|
||||
if !parent_exists(&file) {
|
||||
return Err("下载文件的父级目录不存在!".into());
|
||||
}
|
||||
file
|
||||
}
|
||||
None => {
|
||||
// 如果未指定目标路径,则使用 URL 的文件名
|
||||
let _remote = Path::new(url);
|
||||
let _local = match _remote.file_name() {
|
||||
Some(file) => file,
|
||||
None => return Err("无效的文件名!".into()),
|
||||
};
|
||||
debug!("Get raw name by url: {:?}", _local);
|
||||
abs_path(Path::new(_local))?
|
||||
}
|
||||
};
|
||||
debug!("Download file: {:?}", download_file);
|
||||
|
||||
// 打开或创建文件
|
||||
let mut file = if download_file.exists() {
|
||||
info!("File exists, continue downloading.");
|
||||
OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&download_file)
|
||||
.await?
|
||||
} else {
|
||||
File::create(&download_file).await?
|
||||
};
|
||||
|
||||
// 获取当前文件的大小和总大小
|
||||
let mut current_size = file.metadata().await?.len();
|
||||
let total_size = response.content_length().unwrap();
|
||||
// 如果文件已经完全下载,则返回
|
||||
if current_size == total_size {
|
||||
info!("File already downloaded: {:?}.", download_file);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 如果文件部分下载过,则进行断点续传
|
||||
let mut request = client.get(url);
|
||||
if current_size > 0 {
|
||||
request = request.header("Range", format!("bytes={}-", current_size));
|
||||
file.seek(SeekFrom::Start(current_size)).await?;
|
||||
}
|
||||
|
||||
// 发送请求并获取响应
|
||||
let response = request.send().await?;
|
||||
let mut stream = response.bytes_stream();
|
||||
|
||||
// 设置下载进度条
|
||||
let pb = ProgressBar::new(total_size);
|
||||
pb.set_style(
|
||||
ProgressStyle::default_bar()
|
||||
.template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({bytes_per_sec}, {eta_precise})").unwrap()
|
||||
.progress_chars("#>-"),
|
||||
);
|
||||
|
||||
// 读取响应数据并写入文件
|
||||
while let Some(chunk) = stream.next().await {
|
||||
let chunk = chunk?;
|
||||
file.write_all(&chunk).await?;
|
||||
current_size += chunk.len() as u64;
|
||||
|
||||
// 更新下载进度
|
||||
pb.set_position(current_size);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 检查给定路径的父目录是否存在。
|
||||
///
|
||||
/// 该函数获取路径的父目录并检查该目录是否存在。
|
||||
///
|
||||
/// # 参数
|
||||
/// - `path`: 要检查的路径。
|
||||
///
|
||||
/// # 返回值
|
||||
/// 如果父目录存在则返回 `true`,否则返回 `false`。
|
||||
///
|
||||
/// # 示例
|
||||
/// ```
|
||||
/// use std::path::Path;
|
||||
/// let path = Path::new("/some/directory/file.txt");
|
||||
/// assert_eq!(parent_exists(&path), true);
|
||||
/// ```
|
||||
pub fn parent_exists(path: &Path) -> bool {
|
||||
// 获取路径的父目录,并检查该目录是否存在。
|
||||
match path.parent() {
|
||||
Some(parent) => parent.exists(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取给定路径的绝对路径。
|
||||
///
|
||||
/// 该函数接收一个路径并返回其绝对路径。
|
||||
///
|
||||
/// # 参数
|
||||
/// - `path`: 要转换为绝对路径的相对路径或绝对路径。
|
||||
///
|
||||
/// # 返回值
|
||||
/// 该函数返回一个 `Result` 类型,如果转换成功则为 `Ok(PathBuf)`,如果发生错误则返回错误信息。
|
||||
///
|
||||
/// # 错误
|
||||
/// 如果路径无法转换为绝对路径,该函数会返回一个错误。
|
||||
///
|
||||
/// # 示例
|
||||
/// ```rust
|
||||
/// use std::path::Path;
|
||||
/// let relative_path = Path::new("some/relative/path");
|
||||
/// let absolute_path = abs_path(&relative_path)?;
|
||||
/// println!("Absolute path: {:?}", absolute_path);
|
||||
/// ```
|
||||
pub fn abs_path(path: &Path) -> crate::Result<PathBuf> {
|
||||
// 将路径转换为绝对路径,并将其转换为 PathBuf 类型
|
||||
let abs = path.absolutize()?.to_path_buf();
|
||||
Ok(abs)
|
||||
}
|
55
src/lib.rs
55
src/lib.rs
|
@ -1,2 +1,57 @@
|
|||
use tokio::runtime::Runtime;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
|
||||
/// 包含了一些常用的文件操作函数
|
||||
pub mod file;
|
||||
|
||||
/// 定义 crate::Error
|
||||
/// 大部分函数返回的错误
|
||||
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
/// 定义 crate::Result
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// 创建一个 `tokio` 异步运行时,可用于在同步函数中 block 调用异步方法,例:
|
||||
///
|
||||
/// ```no_run
|
||||
/// let rt = ccutils::async_runtime()?;
|
||||
///
|
||||
/// rt.block_on(async {
|
||||
/// println!("Run as Async");
|
||||
/// })
|
||||
/// ```
|
||||
pub fn async_runtime() -> crate::Result<Runtime> {
|
||||
Ok(Runtime::new()?)
|
||||
}
|
||||
|
||||
/// 设置日志记录配置。
|
||||
///
|
||||
/// 该函数配置日志记录格式、时间戳、行号和环境过滤器。
|
||||
/// 使用 `tracing` 和 `tracing_subscriber` 库进行日志记录设置。
|
||||
///
|
||||
/// # 返回值
|
||||
/// 该函数返回一个 `Result` 类型,如果日志记录设置成功则为 `Ok(())`,如果发生错误则返回错误信息。
|
||||
///
|
||||
/// # 错误
|
||||
/// 如果日志记录初始化失败,该函数会返回一个错误。
|
||||
///
|
||||
/// # 示例
|
||||
/// ```rust
|
||||
/// set_up_logging()?;
|
||||
/// ```
|
||||
pub fn set_up_logging() -> crate::Result<()> {
|
||||
// 配置日志格式化器
|
||||
fmt()
|
||||
// 禁用 ANSI 转义序列(颜色)
|
||||
.with_ansi(false)
|
||||
// 设置时间戳格式为 RFC 3339
|
||||
.with_timer(fmt::time::OffsetTime::local_rfc_3339().unwrap())
|
||||
// 启用行号
|
||||
.with_line_number(true)
|
||||
// 使用环境变量过滤日志级别
|
||||
.with_env_filter(
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
|
||||
)
|
||||
// 尝试初始化日志记录
|
||||
.try_init()
|
||||
}
|
||||
|
|
6
src/main.rs
Normal file
6
src/main.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use ccutils::file;
|
||||
|
||||
fn main() -> ccutils::Result<()> {
|
||||
println!("请运行 `cargo doc --open` 查看文档");
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue
Block a user