use std::fs::File;
use std::io;
#[cfg(unix)]
pub fn pwrite_all(file: &File, offset: u64, buf: &[u8]) -> io::Result<()> {
use std::os::unix::fs::FileExt;
file.write_all_at(buf, offset)
}
#[cfg(windows)]
pub fn pwrite_all(file: &File, mut offset: u64, mut buf: &[u8]) -> io::Result<()> {
use std::os::windows::fs::FileExt;
while !buf.is_empty() {
match file.seek_write(buf, offset) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::WriteZero,
"failed to write whole buffer",
));
}
Ok(n) => {
buf = &buf[n..];
offset += n as u64;
}
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
pub fn extract_filename(url: &str) -> Option<String> {
url::Url::parse(url).ok().and_then(|u| {
u.path_segments()
.and_then(|mut segments| segments.next_back())
.and_then(|s| {
if s.is_empty() {
None
} else {
Some(s.to_string())
}
})
})
}
pub fn parse_content_range_total(value: &str) -> Option<u64> {
let total = value.rsplit('/').next()?.trim();
total.parse::<u64>().ok()
}
pub fn format_speed(bytes_per_sec: f64) -> String {
const UNITS: [&str; 5] = ["B/s", "KB/s", "MB/s", "GB/s", "TB/s"];
let mut v = bytes_per_sec;
let mut i = 0;
while v >= 1024.0 && i < UNITS.len() - 1 {
v /= 1024.0;
i += 1;
}
format!("{v:.2} {}", UNITS[i])
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Read;
#[test]
fn test_extract_filename_from_simple_url() {
let url = "http://example.com/some/path/to/file.zip";
assert_eq!(extract_filename(url), Some("file.zip".to_string()));
}
#[test]
fn test_extract_filename_with_query_params() {
let url = "http://example.com/archive.tar.gz?token=123&user=abc";
assert_eq!(extract_filename(url), Some("archive.tar.gz".to_string()));
}
#[test]
fn test_extract_filename_from_root() {
let url = "http://example.com/";
assert_eq!(extract_filename(url), None);
}
#[test]
fn test_extract_filename_without_extension() {
let url = "http://example.com/some/directory/resource";
assert_eq!(extract_filename(url), Some("resource".to_string()));
}
#[test]
fn test_extract_filename_from_invalid_url() {
let url = "not-a-valid-url";
assert_eq!(extract_filename(url), None);
}
#[test]
fn test_parse_content_range_total() {
assert_eq!(parse_content_range_total("bytes 0-0/12345"), Some(12345));
assert_eq!(parse_content_range_total("bytes 100-199/200"), Some(200));
assert_eq!(parse_content_range_total("bytes 0-0/*"), None);
assert_eq!(parse_content_range_total("garbage"), None);
}
#[test]
fn test_pwrite_all_writes_at_offset() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("pwrite.bin");
let file = File::create(&path).unwrap();
file.set_len(10).unwrap();
pwrite_all(&file, 5, b"WORLD").unwrap();
pwrite_all(&file, 0, b"HELLO").unwrap();
drop(file);
let mut contents = Vec::new();
File::open(&path)
.unwrap()
.read_to_end(&mut contents)
.unwrap();
assert_eq!(&contents, b"HELLOWORLD");
}
#[test]
fn test_format_speed() {
assert_eq!(format_speed(512.0), "512.00 B/s");
assert_eq!(format_speed(1024.0), "1.00 KB/s");
assert_eq!(format_speed(1024.0 * 1024.0 * 2.5), "2.50 MB/s");
}
}