use std::io::Write;
use std::path::Path;
use color_eyre::Result;
use color_eyre::eyre::{self, WrapErr};
use crate::models::dirs;
use crate::store::untar_zst;
use crate::lfs;
use std::fs::{File, OpenOptions};
use tar::Builder;
use zstd::stream::write::Encoder;
use walkdir::WalkDir;
use std::fs;
use std::os::unix::fs::{FileTypeExt, MetadataExt, PermissionsExt};
pub fn unpack_package<P: AsRef<Path>>(epkg_file: P, store_tmp_dir: P) -> Result<()> {
let epkg_file = epkg_file.as_ref();
let store_tmp_dir = store_tmp_dir.as_ref();
untar_zst(
epkg_file.to_str().unwrap(),
store_tmp_dir.to_str().unwrap(),
false
)
}
#[allow(dead_code)]
pub fn unpack_packages(files: Vec<String>) -> Result<()> {
for file in files {
let filename = file.split('/').last()
.ok_or_else(|| eyre::eyre!("Invalid package file name: {}", file))?;
let pkgline = filename.strip_suffix(".epkg")
.ok_or_else(|| eyre::eyre!("File does not have .epkg extension: {}", file))?;
let dir = dirs().epkg_store.join(pkgline);
let dir_str = dir.to_string_lossy().to_owned();
untar_zst(&file, &dir_str, true)
.wrap_err_with(|| format!("Failed to unpack package: {}", file))?;
}
Ok(())
}
pub fn compress_packages(store_dir: &std::path::PathBuf, out_dir: &str, origin_url: &str) -> Result<()> {
let package_txt_path = crate::dirs::path_join(store_dir, &["info", "package.txt"]);
let pkgline = store_dir.file_name().and_then(|os_str| os_str.to_str()).unwrap_or("unknown");
let output_file = Path::new(out_dir).join(&format!("{}.epkg", pkgline));
append_to_file(&package_txt_path, &format!("originUrl: {}", origin_url))?;
compress_folder_to_epkg(store_dir, &output_file.to_string_lossy())?;
println!("{}", output_file.display());
Ok(())
}
pub fn compress_folder_to_epkg(
source_dir: &Path,
output_file: &str,
) -> Result<()> {
let output = File::create(output_file)?;
let encoder = Encoder::new(output, 3)?;
let mut tar_builder = Builder::new(encoder.auto_finish());
let mut entries: Vec<_> = WalkDir::new(source_dir)
.into_iter()
.collect::<Result<Vec<_>, _>>()?;
entries.sort_by(|a, b| a.path().cmp(b.path()));
for entry in entries {
let path = entry.path();
let rel_path = path.strip_prefix(source_dir)?;
if rel_path.as_os_str().is_empty() {
continue;
}
if lfs::is_symlink(path) {
let target = fs::read_link(path)?;
let mut header = tar::Header::new_gnu();
header.set_entry_type(tar::EntryType::Symlink);
header.set_size(0);
header.set_mode(0o755);
header.set_uid(0);
header.set_gid(0);
header.set_mtime(0);
tar_builder.append_link(&mut header, rel_path, &target)?;
} else if entry.file_type().is_file() {
let metadata = lfs::metadata_on_host(path)?;
let mut header = tar::Header::new_gnu();
header.set_entry_type(tar::EntryType::Regular);
header.set_size(metadata.len());
header.set_mode(metadata.permissions().mode());
header.set_mtime(0);
header.set_uid(0);
header.set_gid(0);
tar_builder.append_data(&mut header, rel_path, &mut File::open(path)?)?;
} else if entry.file_type().is_dir() {
let metadata = lfs::metadata_on_host(path)?;
let mut header = tar::Header::new_gnu();
header.set_entry_type(tar::EntryType::Directory);
header.set_size(0);
header.set_mode(metadata.permissions().mode());
header.set_mtime(0);
header.set_uid(0);
header.set_gid(0);
tar_builder.append_data(&mut header, rel_path, std::io::empty())?;
} else {
let metadata = lfs::metadata_on_host(path)?;
let mut header = tar::Header::new_gnu();
if metadata.file_type().is_char_device() {
header.set_entry_type(tar::EntryType::Char);
header.set_device_major((metadata.rdev() >> 8) as u32)?;
header.set_device_minor((metadata.rdev() & 0xff) as u32)?;
} else if metadata.file_type().is_block_device() {
header.set_entry_type(tar::EntryType::Block);
header.set_device_major((metadata.rdev() >> 8) as u32)?;
header.set_device_minor((metadata.rdev() & 0xff) as u32)?;
} else if metadata.file_type().is_fifo() {
header.set_entry_type(tar::EntryType::Fifo);
} else if metadata.file_type().is_socket() {
continue;
} else {
continue;
}
header.set_size(0);
header.set_mode(metadata.permissions().mode());
header.set_mtime(0);
header.set_uid(0);
header.set_gid(0);
tar_builder.append_data(&mut header, rel_path, std::io::empty())?;
}
}
tar_builder.finish()?;
Ok(())
}
pub fn append_to_file(file_path: &Path, content: &str) -> Result<()> {
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open(file_path)?;
writeln!(file, "{}", content)?;
Ok(())
}