use crate::{read_only_guard::ReadOnlyGuard, settings::Settings};
use anyhow::{bail, Result};
use std::{
ffi::OsString,
fs::File,
io::{BufReader, BufWriter, Read, Seek, SeekFrom},
os::unix::ffi::OsStringExt,
path::Path,
};
use zstd::stream::copy_decode;
impl Settings {
pub(crate) fn import_helper(&self, source: &Path) -> Result<()> {
let mut source_file = File::open(source)?;
source_file.seek(SeekFrom::End(0))?;
loop {
let length_of_entry = read_length(&source_file)?;
let Ok(entry_offset) = i64::try_from(length_of_entry) else {
bail!("found length of {length_of_entry} bytes, which is too large to be correct");
};
source_file.seek(SeekFrom::Current(-entry_offset))?;
if source_file.stream_position()? == 0 {
return import_file(&self.settings_path, source_file, length_of_entry);
}
let Ok(length_to_decode) = usize::try_from(length_of_entry) else {
bail!("found length of {length_of_entry}, which is too long for this platform");
};
let file_name_bytes = read_bytes(&source_file, length_to_decode)?;
let file_name = OsString::from_vec(file_name_bytes);
let num_content_bytes = read_length(&source_file)?;
let Ok(content_offset) = i64::try_from(num_content_bytes) else {
bail!(
"found length of {num_content_bytes} bytes, which is too large to be correct"
);
};
source_file.seek(SeekFrom::Current(-content_offset))?;
let destination_path = self.get_mod_file_path(file_name);
import_file(destination_path, &source_file, num_content_bytes)?;
}
}
}
fn import_file(
destination_path: impl AsRef<Path>,
mut source: impl Read + Seek,
num_bytes: u64,
) -> Result<()> {
let reset_point = SeekFrom::Start(source.stream_position()?);
let destination_file = ReadOnlyGuard::new(destination_path, false)?;
copy_decode(
BufReader::new(Read::take(&mut source, num_bytes)),
BufWriter::new(destination_file),
)?;
source.seek(reset_point)?;
Ok(())
}
fn read_length(mut source: impl Read + Seek) -> Result<u64> {
source.seek(std::io::SeekFrom::Current(-8))?;
let reset_point = SeekFrom::Start(source.stream_position()?);
let mut length_bytes = [0_u8; 8];
source.read_exact(&mut length_bytes)?;
let num_bytes = u64::from_le_bytes(length_bytes);
source.seek(reset_point)?;
Ok(num_bytes)
}
fn read_bytes(mut source: impl Read + Seek, num_bytes: usize) -> Result<Vec<u8>> {
let reset_point = SeekFrom::Start(source.stream_position()?);
let mut bytes = vec![0_u8; num_bytes];
source.read_exact(&mut bytes)?;
source.seek(reset_point)?;
Ok(bytes)
}