use std::fs::File;
use std::io::prelude::*;
use std::sync::Arc;
use anyhow::{Context, Result, ensure};
use camino::Utf8Path;
use tracing::*;
use crate::counters;
pub fn check_magic<R: Read>(r: &mut R, expected: &[u8]) -> Result<()> {
let mut magic: Vec<u8> = expected.to_owned();
r.read_exact(&mut magic)?;
ensure!(
magic == expected,
"Expected magic bytes {}, found {}",
unsafe { std::str::from_utf8_unchecked(expected) },
String::from_utf8_lossy(&magic)
);
Ok(())
}
#[derive(Debug)]
pub enum LoadedFile {
Buffered(Vec<u8>),
Mapped(memmap2::Mmap),
}
impl LoadedFile {
pub fn bytes(&self) -> &[u8] {
match self {
LoadedFile::Buffered(vec) => vec,
LoadedFile::Mapped(map) => map,
}
}
}
pub fn read_file(path: &Utf8Path) -> Result<Arc<LoadedFile>> {
const MEGA: u64 = 1024 * 1024;
let mut fh = File::open(path)?;
let file_length = fh.metadata()?.len();
let file = if file_length < 10 * MEGA {
let mut buffer = Vec::with_capacity(file_length as usize);
fh.read_to_end(&mut buffer)?;
counters::bump(counters::Op::FileToBuffer);
LoadedFile::Buffered(buffer)
} else {
let mapping = unsafe { memmap2::Mmap::map(&fh)? };
counters::bump(counters::Op::FileToMmap);
LoadedFile::Mapped(mapping)
};
Ok(Arc::new(file))
}
#[cfg(unix)]
pub fn move_opened<P, Q>(from: P, from_fh: File, to: Q) -> Result<File>
where
P: AsRef<Utf8Path>,
Q: AsRef<Utf8Path>,
{
let from = from.as_ref();
let to = to.as_ref();
match std::fs::rename(from, to) {
Ok(()) => {
trace!("Renamed {from} to {to}");
Ok(from_fh)
},
Err(e) if e.raw_os_error() == Some(18) => {
move_by_copy(from, from_fh, to)
},
Err(e) => anyhow::bail!(e),
}
}
#[cfg(windows)]
pub fn move_opened<P, Q>(from: P, from_fh: File, to: Q) -> Result<File>
where
P: AsRef<Utf8Path>,
Q: AsRef<Utf8Path>,
{
move_by_copy(from.as_ref(), from_fh, to.as_ref())
}
fn move_by_copy(from: &Utf8Path, mut from_fh: File, to: &Utf8Path) -> Result<File> {
from_fh.seek(std::io::SeekFrom::Start(0))?;
let to_fh = safe_copy_to_file(from_fh, to)?;
std::fs::remove_file(from).with_context(|| format!("Couldn't remove {from}"))?;
trace!("Moved {from} to {to}");
Ok(to_fh)
}
pub fn safe_copy_to_file<R: Read>(mut from: R, to: &Utf8Path) -> Result<File> {
let dir = to.parent().unwrap();
let pre = to.file_name().unwrap().to_owned() + ".";
let mut to_fh = tempfile::Builder::new()
.prefix(&pre)
.suffix(".part")
.tempfile_in(dir)
.with_context(|| format!("Couldn't open temporary {to}.part"))?;
let temp_path = format!("{}", to_fh.path().display());
std::io::copy(&mut from, &mut to_fh)
.with_context(|| format!("Couldn't write to {temp_path}"))?;
drop(from);
let persisted = to_fh
.persist(to)
.with_context(|| format!("Couldn't persist {temp_path} to {to}"))?;
persisted
.sync_all()
.with_context(|| format!("Couldn't sync {to}"))?;
Ok(persisted)
}
pub fn nice_size(s: u64) -> String {
use byte_unit::Unit::*;
use byte_unit::*;
let b = Byte::from_u64(s);
let a = b.get_appropriate_unit(UnitType::Decimal); match a.get_unit() {
Bit | B | Kbit | Kibit | KB | KiB => format!("{a:.0}"),
_ => format!("{a:.2}"),
}
}