use std::{
fs::File,
path::{Path, PathBuf},
};
use crate::{
error::{InstallerError, InstallerErrorKind},
manifest::{AppId, AppMetadata, FileType},
};
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct PackageManifest {
pub app_id: AppId,
pub app_metadata: AppMetadata,
pub files: Vec<PackageFileEntry>,
pub interactive_uninstall_args: Vec<String>,
pub quiet_uninstall_args: Vec<String>,
}
impl PackageManifest {
pub fn new(app_id: &AppId) -> Self {
Self {
app_id: app_id.clone(),
app_metadata: AppMetadata::default(),
files: Vec::new(),
interactive_uninstall_args: Vec::new(),
quiet_uninstall_args: Vec::new(),
}
}
pub fn with_self_exe(mut self) -> Result<Self, InstallerError> {
let current_exe_name = crate::os::current_exe_name()?;
self.files.push(PackageFileEntry::new_main_exe(
current_exe_name.clone(),
current_exe_name,
FileType::Executable,
)?);
Ok(self)
}
pub fn with_self_exe_renamed<S: AsRef<str>>(
mut self,
exe_name: S,
) -> Result<Self, InstallerError> {
let current_exe_name = crate::os::current_exe_name()?;
self.files.push(PackageFileEntry::new_main_exe(
current_exe_name,
exe_name.as_ref().into(),
FileType::Executable,
)?);
Ok(self)
}
pub fn with_interactive_uninstall_args(mut self, args: &[&str]) -> Self {
self.interactive_uninstall_args = args.iter().map(|arg| arg.to_string()).collect();
self
}
pub fn with_quiet_uninstall_args(mut self, args: &[&str]) -> Self {
self.quiet_uninstall_args = args.iter().map(|arg| arg.to_string()).collect();
self
}
pub fn with_file_entry<P: AsRef<Path>>(
mut self,
package_path: P,
file_type: FileType,
) -> Result<Self, InstallerError> {
self.files.push(PackageFileEntry::new(
package_path.as_ref(),
package_path.as_ref(),
file_type,
)?);
Ok(self)
}
pub fn with_file_entry_renamed<P: AsRef<Path>>(
mut self,
package_path: P,
target_path: P,
file_type: FileType,
) -> Result<Self, InstallerError> {
self.files
.push(PackageFileEntry::new(package_path, target_path, file_type)?);
Ok(self)
}
pub fn main_executable(&self) -> Option<&PackageFileEntry> {
self.files.iter().find(|entry| entry.is_main_executable)
}
pub fn verify<P: AsRef<Path>>(&self, source_dir: P) -> Result<(), PackageVerifyError> {
self.main_executable()
.ok_or(PackageVerifyError::MissingMainExecutable)?;
let source_dir = source_dir.as_ref();
for entry in &self.files {
let source_path = source_dir.join(entry.package_path());
let _ = File::open(&source_path).map_err(|source| PackageVerifyError::InvalidFile {
path: source_path.clone(),
source,
})?;
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct PackageFileEntry {
package_path: PathBuf,
target_path: PathBuf,
file_type: FileType,
is_main_executable: bool,
}
impl PackageFileEntry {
pub fn new<P: AsRef<Path>>(
package_path: P,
target_path: P,
file_type: FileType,
) -> Result<Self, PackagePathError> {
Self::new_impl(package_path, target_path, file_type, false)
}
pub fn new_main_exe<P: AsRef<Path>>(
package_path: P,
target_path: P,
file_type: FileType,
) -> Result<Self, PackagePathError> {
Self::new_impl(package_path, target_path, file_type, true)
}
fn new_impl<P: AsRef<Path>>(
package_path: P,
target_path: P,
file_type: FileType,
is_main_executable: bool,
) -> Result<Self, PackagePathError> {
Self::validate_path(package_path.as_ref())?;
Self::validate_path(target_path.as_ref())?;
Ok(Self {
package_path: package_path.as_ref().to_owned(),
target_path: target_path.as_ref().to_owned(),
file_type,
is_main_executable,
})
}
fn validate_path(path: &Path) -> Result<(), PackagePathError> {
for component in path.components() {
match component {
std::path::Component::Normal(_) => continue,
_ => return Err(PackagePathError::new(path.to_path_buf())),
}
}
Ok(())
}
pub fn package_path(&self) -> &PathBuf {
&self.package_path
}
pub fn target_path(&self) -> &PathBuf {
&self.target_path
}
pub fn file_type(&self) -> FileType {
self.file_type
}
pub fn is_main_executable(&self) -> bool {
self.is_main_executable
}
}
#[derive(Debug, thiserror::Error)]
#[error("package path error: {path}")]
pub struct PackagePathError {
path: PathBuf,
}
impl PackagePathError {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn path(&self) -> &PathBuf {
&self.path
}
}
impl From<PackagePathError> for InstallerError {
fn from(value: PackagePathError) -> Self {
InstallerError::new(InstallerErrorKind::InvalidPackageManifest).with_source(value)
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PackageVerifyError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("missing main executable")]
MissingMainExecutable,
#[error("invalid file {path}")]
InvalidFile {
path: PathBuf,
#[source]
source: std::io::Error,
},
}
impl From<PackageVerifyError> for InstallerError {
fn from(value: PackageVerifyError) -> Self {
InstallerError::new(InstallerErrorKind::InvalidPackageManifest).with_source(value)
}
}