use std::path::PathBuf;
use crate::artifact::{ ArtifactKind, ArtifactLayout };
use crate::error::AssetError;
use crate::paths::AssetPaths;
#[ derive( Debug ) ]
pub struct InstallReport< A >
{
pub kind : ArtifactKind,
pub name : String,
pub action : A,
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub enum InstallOutcome
{
Installed,
Reinstalled,
}
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub enum UninstallOutcome
{
Uninstalled,
NotInstalled,
}
#[ inline ]
pub fn install( paths : &AssetPaths, kind : ArtifactKind, name : &str ) -> Result< InstallReport< InstallOutcome >, AssetError >
{
let src_path = source_path( paths, kind, name );
if !src_path.exists()
{
return Err( AssetError::SourceNotFound
{
kind : kind.as_str().to_string(),
name : name.to_string(),
} );
}
let tgt_dir = paths.target_dir( kind );
let tgt_path = target_path( paths, kind, name );
std::fs::create_dir_all( &tgt_dir ).map_err( AssetError::Io )?;
let action = if std::fs::symlink_metadata( &tgt_path ).is_ok()
{
let meta = std::fs::symlink_metadata( &tgt_path ).map_err( AssetError::Io )?;
if !meta.file_type().is_symlink()
{
return Err( AssetError::NotASymlink
{
kind : kind.as_str().to_string(),
name : name.to_string(),
} );
}
std::fs::remove_file( &tgt_path ).map_err( AssetError::Io )?;
InstallOutcome::Reinstalled
}
else
{
InstallOutcome::Installed
};
create_symlink( &src_path, &tgt_path, kind.layout() ).map_err( AssetError::Io )?;
Ok( InstallReport { kind, name : name.to_string(), action } )
}
#[ inline ]
pub fn uninstall( paths : &AssetPaths, kind : ArtifactKind, name : &str ) -> Result< InstallReport< UninstallOutcome >, AssetError >
{
let tgt_path = target_path( paths, kind, name );
let meta = match std::fs::symlink_metadata( &tgt_path )
{
Ok( m ) => m,
Err( e ) if e.kind() == std::io::ErrorKind::NotFound =>
{
return Ok( InstallReport
{
kind,
name : name.to_string(),
action : UninstallOutcome::NotInstalled,
} );
}
Err( e ) => return Err( AssetError::Io( e ) ),
};
if !meta.file_type().is_symlink()
{
return Err( AssetError::NotASymlink
{
kind : kind.as_str().to_string(),
name : name.to_string(),
} );
}
std::fs::remove_file( &tgt_path ).map_err( AssetError::Io )?;
Ok( InstallReport { kind, name : name.to_string(), action : UninstallOutcome::Uninstalled } )
}
#[ cfg( unix ) ]
#[ inline ]
fn create_symlink( src : &std::path::Path, dst : &std::path::Path, _layout : ArtifactLayout ) -> std::io::Result<()>
{
std::os::unix::fs::symlink( src, dst )
}
#[ cfg( windows ) ]
#[ inline ]
fn create_symlink( src : &std::path::Path, dst : &std::path::Path, layout : ArtifactLayout ) -> std::io::Result<()>
{
match layout
{
ArtifactLayout::File => std::os::windows::fs::symlink_file( src, dst ),
ArtifactLayout::Directory => std::os::windows::fs::symlink_dir( src, dst ),
}
}
fn artifact_path( base_dir : &std::path::Path, kind : ArtifactKind, name : &str ) -> PathBuf
{
match kind.layout()
{
ArtifactLayout::File =>
{
let ext = kind.file_extension().unwrap_or( "" );
base_dir.join( format!( "{name}.{ext}" ) )
}
ArtifactLayout::Directory => base_dir.join( name ),
}
}
fn source_path( paths : &AssetPaths, kind : ArtifactKind, name : &str ) -> PathBuf
{
artifact_path( &paths.source_dir( kind ), kind, name )
}
fn target_path( paths : &AssetPaths, kind : ArtifactKind, name : &str ) -> PathBuf
{
artifact_path( &paths.target_dir( kind ), kind, name )
}