152 lines
4.4 KiB
Rust
152 lines
4.4 KiB
Rust
use std::fs::{self, File};
|
|
use std::io::Read;
|
|
use std::path::Path;
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
use anyhow::{anyhow, Context, Result};
|
|
use ed2k::digest::Digest;
|
|
use ed2k::Ed2k;
|
|
use md5::Md5;
|
|
use serde::{Deserialize, Serialize};
|
|
use sha1::Sha1;
|
|
use sha2::Sha256;
|
|
use xxhash_rust::xxh3::Xxh3;
|
|
|
|
use crate::constants::{DEFAULT_BUFFER_SIZE, HEAD_115_BYTES, HEAD_BAIDU_BYTES};
|
|
use crate::head_hash::{calc_head_115, calc_head_baidu, HeadChunk};
|
|
use crate::utils::{basename, friendly_size, hex_upper};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct FileMeta {
|
|
pub basename: String,
|
|
pub size: u64,
|
|
pub friendly_size: String,
|
|
pub mtime: i64,
|
|
pub head_115: String,
|
|
pub head_baidu: String,
|
|
pub ed2k: String,
|
|
pub md5: String,
|
|
pub sha1: String,
|
|
pub sha256: String,
|
|
pub xxh128: String,
|
|
}
|
|
|
|
impl FileMeta {
|
|
pub fn from_path_with_callback<F1, F2>(
|
|
path: &Path,
|
|
mut on_bytes_read: F1,
|
|
mut on_iop: F2,
|
|
) -> Result<Self>
|
|
where
|
|
F1: FnMut(u64),
|
|
F2: FnMut(),
|
|
{
|
|
let info =
|
|
fs::metadata(path).with_context(|| format!("无法读取文件信息: {}", path.display()))?;
|
|
if !info.is_file() {
|
|
return Err(anyhow!("{} 不是文件", path.display()));
|
|
}
|
|
|
|
let basename_str = basename(
|
|
path.file_name()
|
|
.ok_or_else(|| anyhow!("{} 缺少文件名", path.display()))?,
|
|
);
|
|
let size = info.len();
|
|
let friendly = friendly_size(size);
|
|
let mtime = info
|
|
.modified()
|
|
.unwrap_or(SystemTime::UNIX_EPOCH)
|
|
.duration_since(UNIX_EPOCH)
|
|
.map(|d| d.as_secs() as i64)
|
|
.unwrap_or(0);
|
|
|
|
let mut file =
|
|
File::open(path).with_context(|| format!("无法打开文件: {}", path.display()))?;
|
|
|
|
let mut buffer = vec![0u8; DEFAULT_BUFFER_SIZE];
|
|
let mut md5_hasher = Md5::new();
|
|
let mut sha1_hasher = Sha1::new();
|
|
let mut sha256_hasher = Sha256::new();
|
|
let mut xxh_hasher = Xxh3::new();
|
|
let mut ed2k_hasher = Ed2k::new();
|
|
let mut head115 = HeadChunk::new(HEAD_115_BYTES);
|
|
let mut head_baidu = HeadChunk::new(HEAD_BAIDU_BYTES);
|
|
|
|
loop {
|
|
let read_len = file.read(&mut buffer)?;
|
|
if read_len == 0 {
|
|
break;
|
|
}
|
|
let chunk = &buffer[..read_len];
|
|
md5_hasher.update(chunk);
|
|
sha1_hasher.update(chunk);
|
|
sha256_hasher.update(chunk);
|
|
xxh_hasher.update(chunk);
|
|
ed2k_hasher.update(chunk);
|
|
|
|
head115.feed(chunk);
|
|
head_baidu.feed(chunk);
|
|
|
|
on_bytes_read(read_len as u64);
|
|
on_iop(); // 每次 read 调用算一次 IOPS
|
|
}
|
|
|
|
let head_115 = calc_head_115(head115.as_slice());
|
|
let head_baidu = calc_head_baidu(head_baidu.as_slice());
|
|
|
|
let md5_hex = hex_upper(md5_hasher.finalize());
|
|
let sha1_hex = hex_upper(sha1_hasher.finalize());
|
|
let sha256_hex = hex_upper(sha256_hasher.finalize());
|
|
let xxh_hex = hex_upper(xxh_hasher.digest128().to_be_bytes());
|
|
let ed2k_hex = hex_upper(ed2k_hasher.finalize());
|
|
|
|
Ok(Self {
|
|
basename: basename_str,
|
|
size,
|
|
friendly_size: friendly,
|
|
mtime,
|
|
head_115,
|
|
head_baidu,
|
|
ed2k: ed2k_hex,
|
|
md5: md5_hex,
|
|
sha1: sha1_hex,
|
|
sha256: sha256_hex,
|
|
xxh128: xxh_hex,
|
|
})
|
|
}
|
|
|
|
pub fn from_reader<R: Read>(reader: R) -> Result<Self> {
|
|
Ok(serde_json::from_reader(reader)?)
|
|
}
|
|
|
|
pub fn to_pretty_json(&self) -> Result<String> {
|
|
Ok(serde_json::to_string_pretty(self)?)
|
|
}
|
|
}
|
|
|
|
pub fn calc_xxh128_with_callback<F1, F2>(
|
|
path: &Path,
|
|
mut on_bytes_read: F1,
|
|
mut on_iop: F2,
|
|
) -> Result<String>
|
|
where
|
|
F1: FnMut(u64),
|
|
F2: FnMut(),
|
|
{
|
|
let mut file = File::open(path).with_context(|| format!("无法打开文件: {}", path.display()))?;
|
|
let mut buffer = vec![0u8; DEFAULT_BUFFER_SIZE];
|
|
let mut hasher = Xxh3::new();
|
|
|
|
loop {
|
|
let read_len = file.read(&mut buffer)?;
|
|
if read_len == 0 {
|
|
break;
|
|
}
|
|
hasher.update(&buffer[..read_len]);
|
|
on_bytes_read(read_len as u64);
|
|
on_iop(); // 每次 read 调用算一次 IOPS
|
|
}
|
|
|
|
Ok(hex_upper(hasher.digest128().to_be_bytes()))
|
|
}
|