...
This commit is contained in:
522
packages/system/os/src/download.rs
Normal file
522
packages/system/os/src/download.rs
Normal file
@@ -0,0 +1,522 @@
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
// Define a custom error type for download operations
|
||||
#[derive(Debug)]
|
||||
pub enum DownloadError {
|
||||
CreateDirectoryFailed(io::Error),
|
||||
CurlExecutionFailed(io::Error),
|
||||
DownloadFailed(String),
|
||||
FileMetadataError(io::Error),
|
||||
FileTooSmall(i64, i64),
|
||||
RemoveFileFailed(io::Error),
|
||||
ExtractionFailed(String),
|
||||
CommandExecutionFailed(io::Error),
|
||||
InvalidUrl(String),
|
||||
NotAFile(String),
|
||||
PlatformNotSupported(String),
|
||||
InstallationFailed(String),
|
||||
}
|
||||
|
||||
// Implement Display for DownloadError
|
||||
impl fmt::Display for DownloadError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
DownloadError::CreateDirectoryFailed(e) => {
|
||||
write!(f, "Error creating directories: {}", e)
|
||||
}
|
||||
DownloadError::CurlExecutionFailed(e) => write!(f, "Error executing curl: {}", e),
|
||||
DownloadError::DownloadFailed(url) => write!(f, "Error downloading url: {}", url),
|
||||
DownloadError::FileMetadataError(e) => write!(f, "Error getting file metadata: {}", e),
|
||||
DownloadError::FileTooSmall(size, min) => write!(
|
||||
f,
|
||||
"Error: Downloaded file is too small ({}KB < {}KB)",
|
||||
size, min
|
||||
),
|
||||
DownloadError::RemoveFileFailed(e) => write!(f, "Error removing file: {}", e),
|
||||
DownloadError::ExtractionFailed(e) => write!(f, "Error extracting archive: {}", e),
|
||||
DownloadError::CommandExecutionFailed(e) => write!(f, "Error executing command: {}", e),
|
||||
DownloadError::InvalidUrl(url) => write!(f, "Invalid URL: {}", url),
|
||||
DownloadError::NotAFile(path) => write!(f, "Not a file: {}", path),
|
||||
DownloadError::PlatformNotSupported(msg) => write!(f, "{}", msg),
|
||||
DownloadError::InstallationFailed(msg) => write!(f, "{}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Error trait for DownloadError
|
||||
impl Error for DownloadError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
DownloadError::CreateDirectoryFailed(e) => Some(e),
|
||||
DownloadError::CurlExecutionFailed(e) => Some(e),
|
||||
DownloadError::FileMetadataError(e) => Some(e),
|
||||
DownloadError::RemoveFileFailed(e) => Some(e),
|
||||
DownloadError::CommandExecutionFailed(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file from URL to destination using the curl command.
|
||||
* This function is primarily intended for downloading archives that will be extracted
|
||||
* to a directory.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `url` - The URL to download from
|
||||
* * `dest` - The destination directory where the file will be saved or extracted
|
||||
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(String)` - The path where the file was saved or extracted
|
||||
* * `Err(DownloadError)` - An error if the download failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```no_run
|
||||
* use sal_os::download;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Download a file with no minimum size requirement
|
||||
* let path = download("https://example.com/file.txt", "/tmp/", 0)?;
|
||||
*
|
||||
* // Download a file with minimum size requirement of 100KB
|
||||
* let path = download("https://example.com/file.zip", "/tmp/", 100)?;
|
||||
*
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* # Notes
|
||||
*
|
||||
* If the URL ends with .tar.gz, .tgz, .tar, or .zip, the file will be automatically
|
||||
* extracted to the destination directory.
|
||||
*/
|
||||
pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
||||
// Create parent directories if they don't exist
|
||||
let dest_path = Path::new(dest);
|
||||
fs::create_dir_all(dest_path).map_err(DownloadError::CreateDirectoryFailed)?;
|
||||
|
||||
// Extract filename from URL
|
||||
let filename = match url.split('/').last() {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
return Err(DownloadError::InvalidUrl(
|
||||
"cannot extract filename".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Create a full path for the downloaded file
|
||||
let file_path = format!("{}/{}", dest.trim_end_matches('/'), filename);
|
||||
|
||||
// Create a temporary path for downloading
|
||||
let temp_path = format!("{}.download", file_path);
|
||||
|
||||
// Use curl to download the file with progress bar
|
||||
println!("Downloading {} to {}", url, file_path);
|
||||
let output = Command::new("curl")
|
||||
.args(&[
|
||||
"--progress-bar",
|
||||
"--location",
|
||||
"--fail",
|
||||
"--output",
|
||||
&temp_path,
|
||||
url,
|
||||
])
|
||||
.status()
|
||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||
|
||||
if !output.success() {
|
||||
return Err(DownloadError::DownloadFailed(url.to_string()));
|
||||
}
|
||||
|
||||
// Show file size after download
|
||||
match fs::metadata(&temp_path) {
|
||||
Ok(metadata) => {
|
||||
let size_bytes = metadata.len();
|
||||
let size_kb = size_bytes / 1024;
|
||||
let size_mb = size_kb / 1024;
|
||||
if size_mb > 1 {
|
||||
println!(
|
||||
"Download complete! File size: {:.2} MB",
|
||||
size_bytes as f64 / (1024.0 * 1024.0)
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"Download complete! File size: {:.2} KB",
|
||||
size_bytes as f64 / 1024.0
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(_) => println!("Download complete!"),
|
||||
}
|
||||
|
||||
// Check file size if minimum size is specified
|
||||
if min_size_kb > 0 {
|
||||
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
|
||||
let size_kb = metadata.len() as i64 / 1024;
|
||||
if size_kb < min_size_kb {
|
||||
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
|
||||
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a compressed file that needs extraction
|
||||
let lower_url = url.to_lowercase();
|
||||
let is_archive = lower_url.ends_with(".tar.gz")
|
||||
|| lower_url.ends_with(".tgz")
|
||||
|| lower_url.ends_with(".tar")
|
||||
|| lower_url.ends_with(".zip");
|
||||
|
||||
if is_archive {
|
||||
// Extract the file using the appropriate command with progress indication
|
||||
println!("Extracting {} to {}", temp_path, dest);
|
||||
let output = if lower_url.ends_with(".zip") {
|
||||
Command::new("unzip")
|
||||
.args(&["-o", &temp_path, "-d", dest]) // Removed -q for verbosity
|
||||
.status()
|
||||
} else if lower_url.ends_with(".tar.gz") || lower_url.ends_with(".tgz") {
|
||||
Command::new("tar")
|
||||
.args(&["-xzvf", &temp_path, "-C", dest]) // Added v for verbosity
|
||||
.status()
|
||||
} else {
|
||||
Command::new("tar")
|
||||
.args(&["-xvf", &temp_path, "-C", dest]) // Added v for verbosity
|
||||
.status()
|
||||
};
|
||||
|
||||
match output {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
return Err(DownloadError::ExtractionFailed(
|
||||
"Error extracting archive".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
||||
}
|
||||
|
||||
// Show number of extracted files
|
||||
match fs::read_dir(dest) {
|
||||
Ok(entries) => {
|
||||
let count = entries.count();
|
||||
println!("Extraction complete! Extracted {} files/directories", count);
|
||||
}
|
||||
Err(_) => println!("Extraction complete!"),
|
||||
}
|
||||
|
||||
// Remove the temporary file
|
||||
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
|
||||
|
||||
Ok(dest.to_string())
|
||||
} else {
|
||||
// Just rename the temporary file to the final destination
|
||||
fs::rename(&temp_path, &file_path).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
|
||||
|
||||
Ok(file_path)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file from URL to a specific file destination using the curl command.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `url` - The URL to download from
|
||||
* * `dest` - The destination file path where the file will be saved
|
||||
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(String)` - The path where the file was saved
|
||||
* * `Err(DownloadError)` - An error if the download failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```no_run
|
||||
* use sal_os::download_file;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Download a file with no minimum size requirement
|
||||
* let path = download_file("https://example.com/file.txt", "/tmp/file.txt", 0)?;
|
||||
*
|
||||
* // Download a file with minimum size requirement of 100KB
|
||||
* let path = download_file("https://example.com/file.zip", "/tmp/file.zip", 100)?;
|
||||
*
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn download_file(url: &str, dest: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
||||
// Create parent directories if they don't exist
|
||||
let dest_path = Path::new(dest);
|
||||
if let Some(parent) = dest_path.parent() {
|
||||
fs::create_dir_all(parent).map_err(DownloadError::CreateDirectoryFailed)?;
|
||||
}
|
||||
|
||||
// Create a temporary path for downloading
|
||||
let temp_path = format!("{}.download", dest);
|
||||
|
||||
// Use curl to download the file with progress bar
|
||||
println!("Downloading {} to {}", url, dest);
|
||||
let output = Command::new("curl")
|
||||
.args(&[
|
||||
"--progress-bar",
|
||||
"--location",
|
||||
"--fail",
|
||||
"--output",
|
||||
&temp_path,
|
||||
url,
|
||||
])
|
||||
.status()
|
||||
.map_err(DownloadError::CurlExecutionFailed)?;
|
||||
|
||||
if !output.success() {
|
||||
return Err(DownloadError::DownloadFailed(url.to_string()));
|
||||
}
|
||||
|
||||
// Show file size after download
|
||||
match fs::metadata(&temp_path) {
|
||||
Ok(metadata) => {
|
||||
let size_bytes = metadata.len();
|
||||
let size_kb = size_bytes / 1024;
|
||||
let size_mb = size_kb / 1024;
|
||||
if size_mb > 1 {
|
||||
println!(
|
||||
"Download complete! File size: {:.2} MB",
|
||||
size_bytes as f64 / (1024.0 * 1024.0)
|
||||
);
|
||||
} else {
|
||||
println!(
|
||||
"Download complete! File size: {:.2} KB",
|
||||
size_bytes as f64 / 1024.0
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(_) => println!("Download complete!"),
|
||||
}
|
||||
|
||||
// Check file size if minimum size is specified
|
||||
if min_size_kb > 0 {
|
||||
let metadata = fs::metadata(&temp_path).map_err(DownloadError::FileMetadataError)?;
|
||||
let size_kb = metadata.len() as i64 / 1024;
|
||||
if size_kb < min_size_kb {
|
||||
fs::remove_file(&temp_path).map_err(DownloadError::RemoveFileFailed)?;
|
||||
return Err(DownloadError::FileTooSmall(size_kb, min_size_kb));
|
||||
}
|
||||
}
|
||||
|
||||
// Rename the temporary file to the final destination
|
||||
fs::rename(&temp_path, dest).map_err(|e| DownloadError::CreateDirectoryFailed(e))?;
|
||||
|
||||
Ok(dest.to_string())
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a file executable (equivalent to chmod +x).
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `path` - The path to the file to make executable
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(String)` - A success message including the path
|
||||
* * `Err(DownloadError)` - An error if the operation failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```no_run
|
||||
* use sal_os::chmod_exec;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Make a file executable
|
||||
* chmod_exec("/path/to/file")?;
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pub fn chmod_exec(path: &str) -> Result<String, DownloadError> {
|
||||
let path_obj = Path::new(path);
|
||||
|
||||
// Check if the path exists and is a file
|
||||
if !path_obj.exists() {
|
||||
return Err(DownloadError::NotAFile(format!(
|
||||
"Path does not exist: {}",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
if !path_obj.is_file() {
|
||||
return Err(DownloadError::NotAFile(format!(
|
||||
"Path is not a file: {}",
|
||||
path
|
||||
)));
|
||||
}
|
||||
|
||||
// Get current permissions
|
||||
let metadata = fs::metadata(path).map_err(DownloadError::FileMetadataError)?;
|
||||
let mut permissions = metadata.permissions();
|
||||
|
||||
// Set executable bit for user, group, and others
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mode = permissions.mode();
|
||||
// Add executable bit for user, group, and others (equivalent to +x)
|
||||
let new_mode = mode | 0o111;
|
||||
permissions.set_mode(new_mode);
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
// On non-Unix platforms, we can't set executable bit directly
|
||||
// Just return success with a warning
|
||||
return Ok(format!(
|
||||
"Made {} executable (note: non-Unix platform, may not be fully supported)",
|
||||
path
|
||||
));
|
||||
}
|
||||
|
||||
// Apply the new permissions
|
||||
fs::set_permissions(path, permissions).map_err(|e| {
|
||||
DownloadError::CommandExecutionFailed(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("Failed to set executable permissions: {}", e),
|
||||
))
|
||||
})?;
|
||||
|
||||
Ok(format!("Made {} executable", path))
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file and install it if it's a supported package format.
|
||||
*
|
||||
* # Arguments
|
||||
*
|
||||
* * `url` - The URL to download from
|
||||
* * `min_size_kb` - Minimum required file size in KB (0 for no minimum)
|
||||
*
|
||||
* # Returns
|
||||
*
|
||||
* * `Ok(String)` - The path where the file was saved or extracted
|
||||
* * `Err(DownloadError)` - An error if the download or installation failed
|
||||
*
|
||||
* # Examples
|
||||
*
|
||||
* ```no_run
|
||||
* use sal_os::download_install;
|
||||
*
|
||||
* fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
* // Download and install a .deb package
|
||||
* let result = download_install("https://example.com/package.deb", 100)?;
|
||||
* Ok(())
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* # Notes
|
||||
*
|
||||
* Currently only supports .deb packages on Debian-based systems.
|
||||
* For other file types, it behaves the same as the download function.
|
||||
*/
|
||||
pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, DownloadError> {
|
||||
// Extract filename from URL
|
||||
let filename = match url.split('/').last() {
|
||||
Some(name) => name,
|
||||
None => {
|
||||
return Err(DownloadError::InvalidUrl(
|
||||
"cannot extract filename".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// Create a proper destination path
|
||||
let dest_path = format!("/tmp/{}", filename);
|
||||
|
||||
// Check if it's a compressed file that needs extraction
|
||||
let lower_url = url.to_lowercase();
|
||||
let is_archive = lower_url.ends_with(".tar.gz")
|
||||
|| lower_url.ends_with(".tgz")
|
||||
|| lower_url.ends_with(".tar")
|
||||
|| lower_url.ends_with(".zip");
|
||||
|
||||
let download_result = if is_archive {
|
||||
// For archives, use the directory-based download function
|
||||
download(url, "/tmp", min_size_kb)?
|
||||
} else {
|
||||
// For regular files, use the file-specific download function
|
||||
download_file(url, &dest_path, min_size_kb)?
|
||||
};
|
||||
|
||||
// Check if the downloaded result is a file
|
||||
let path = Path::new(&dest_path);
|
||||
if !path.is_file() {
|
||||
return Ok(download_result); // Not a file, might be an extracted directory
|
||||
}
|
||||
|
||||
// Check if it's a .deb package
|
||||
if dest_path.to_lowercase().ends_with(".deb") {
|
||||
// Check if we're on a Debian-based platform
|
||||
let platform_check = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg("command -v dpkg > /dev/null && command -v apt > /dev/null || test -f /etc/debian_version")
|
||||
.status();
|
||||
|
||||
match platform_check {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
return Err(DownloadError::PlatformNotSupported(
|
||||
"Cannot install .deb package: not on a Debian-based system".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(DownloadError::PlatformNotSupported(
|
||||
"Failed to check system compatibility for .deb installation".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Install the .deb package non-interactively
|
||||
println!("Installing package: {}", dest_path);
|
||||
let install_result = Command::new("sudo")
|
||||
.args(&["dpkg", "--install", &dest_path])
|
||||
.status();
|
||||
|
||||
match install_result {
|
||||
Ok(status) => {
|
||||
if !status.success() {
|
||||
// If dpkg fails, try to fix dependencies and retry
|
||||
println!("Attempting to resolve dependencies...");
|
||||
let fix_deps = Command::new("sudo")
|
||||
.args(&["apt-get", "install", "-f", "-y"])
|
||||
.status();
|
||||
|
||||
if let Ok(fix_status) = fix_deps {
|
||||
if !fix_status.success() {
|
||||
return Err(DownloadError::InstallationFailed(
|
||||
"Failed to resolve package dependencies".to_string(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(DownloadError::InstallationFailed(
|
||||
"Failed to resolve package dependencies".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
println!("Package installation completed successfully");
|
||||
}
|
||||
Err(e) => return Err(DownloadError::CommandExecutionFailed(e)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(download_result)
|
||||
}
|
1185
packages/system/os/src/fs.rs
Normal file
1185
packages/system/os/src/fs.rs
Normal file
File diff suppressed because it is too large
Load Diff
13
packages/system/os/src/lib.rs
Normal file
13
packages/system/os/src/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
pub mod download;
|
||||
pub mod fs;
|
||||
pub mod package;
|
||||
pub mod platform;
|
||||
|
||||
// Re-export all public functions and types
|
||||
pub use download::*;
|
||||
pub use fs::*;
|
||||
pub use package::*;
|
||||
pub use platform::*;
|
||||
|
||||
// Rhai integration module
|
||||
pub mod rhai;
|
1003
packages/system/os/src/package.rs
Normal file
1003
packages/system/os/src/package.rs
Normal file
File diff suppressed because it is too large
Load Diff
75
packages/system/os/src/platform.rs
Normal file
75
packages/system/os/src/platform.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PlatformError {
|
||||
#[error("{0}: {1}")]
|
||||
Generic(String, String),
|
||||
}
|
||||
|
||||
impl PlatformError {
|
||||
pub fn new(kind: &str, message: &str) -> Self {
|
||||
PlatformError::Generic(kind.to_string(), message.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn is_osx() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub fn is_osx() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn is_linux() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn is_linux() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn is_arm() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
pub fn is_arm() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn is_x86() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86_64"))]
|
||||
pub fn is_x86() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn check_linux_x86() -> Result<(), PlatformError> {
|
||||
if is_linux() && is_x86() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PlatformError::new(
|
||||
"Platform Check Error",
|
||||
"This operation is only supported on Linux x86_64.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_macos_arm() -> Result<(), PlatformError> {
|
||||
if is_osx() && is_arm() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PlatformError::new(
|
||||
"Platform Check Error",
|
||||
"This operation is only supported on macOS ARM.",
|
||||
))
|
||||
}
|
||||
}
|
424
packages/system/os/src/rhai.rs
Normal file
424
packages/system/os/src/rhai.rs
Normal file
@@ -0,0 +1,424 @@
|
||||
//! Rhai wrappers for OS module functions
|
||||
//!
|
||||
//! This module provides Rhai wrappers for the functions in the OS module.
|
||||
|
||||
use crate::package::PackHero;
|
||||
use crate::{download as dl, fs, package};
|
||||
use rhai::{Array, Engine, EvalAltResult, Position};
|
||||
|
||||
/// A trait for converting a Result to a Rhai-compatible error
|
||||
pub trait ToRhaiError<T> {
|
||||
fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>>;
|
||||
}
|
||||
|
||||
impl<T, E: std::error::Error> ToRhaiError<T> for Result<T, E> {
|
||||
fn to_rhai_error(self) -> Result<T, Box<EvalAltResult>> {
|
||||
self.map_err(|e| {
|
||||
Box::new(EvalAltResult::ErrorRuntime(
|
||||
e.to_string().into(),
|
||||
Position::NONE,
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Register OS module functions with the Rhai engine
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `engine` - The Rhai engine to register the functions with
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<(), Box<EvalAltResult>>` - Ok if registration was successful, Err otherwise
|
||||
pub fn register_os_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Register file system functions
|
||||
engine.register_fn("copy", copy);
|
||||
engine.register_fn("copy_bin", copy_bin);
|
||||
engine.register_fn("exist", exist);
|
||||
engine.register_fn("find_file", find_file);
|
||||
engine.register_fn("find_files", find_files);
|
||||
engine.register_fn("find_dir", find_dir);
|
||||
engine.register_fn("find_dirs", find_dirs);
|
||||
engine.register_fn("delete", delete);
|
||||
engine.register_fn("mkdir", mkdir);
|
||||
engine.register_fn("file_size", file_size);
|
||||
engine.register_fn("rsync", rsync);
|
||||
engine.register_fn("chdir", chdir);
|
||||
engine.register_fn("file_read", file_read);
|
||||
engine.register_fn("file_write", file_write);
|
||||
engine.register_fn("file_write_append", file_write_append);
|
||||
|
||||
// Register command check functions
|
||||
engine.register_fn("which", which);
|
||||
engine.register_fn("cmd_ensure_exists", cmd_ensure_exists);
|
||||
|
||||
// Register download functions
|
||||
engine.register_fn("download", download);
|
||||
engine.register_fn("download_file", download_file);
|
||||
engine.register_fn("download_install", download_install);
|
||||
engine.register_fn("chmod_exec", chmod_exec);
|
||||
|
||||
// Register move function
|
||||
engine.register_fn("mv", mv);
|
||||
|
||||
// Register package management functions
|
||||
engine.register_fn("package_install", package_install);
|
||||
engine.register_fn("package_remove", package_remove);
|
||||
engine.register_fn("package_update", package_update);
|
||||
engine.register_fn("package_upgrade", package_upgrade);
|
||||
engine.register_fn("package_list", package_list);
|
||||
engine.register_fn("package_search", package_search);
|
||||
engine.register_fn("package_is_installed", package_is_installed);
|
||||
engine.register_fn("package_set_debug", package_set_debug);
|
||||
engine.register_fn("package_platform", package_platform);
|
||||
|
||||
// Register platform detection functions
|
||||
engine.register_fn("platform_is_osx", platform_is_osx);
|
||||
engine.register_fn("platform_is_linux", platform_is_linux);
|
||||
engine.register_fn("platform_is_arm", platform_is_arm);
|
||||
engine.register_fn("platform_is_x86", platform_is_x86);
|
||||
engine.register_fn("platform_check_linux_x86", platform_check_linux_x86);
|
||||
engine.register_fn("platform_check_macos_arm", platform_check_macos_arm);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//
|
||||
// File System Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for fs::copy
|
||||
///
|
||||
/// Recursively copy a file or directory from source to destination.
|
||||
pub fn copy(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::copy(src, dest).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::copy_bin
|
||||
///
|
||||
/// Copy a binary to the correct location based on OS and user privileges.
|
||||
pub fn copy_bin(src: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::copy_bin(src).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::exist
|
||||
///
|
||||
/// Check if a file or directory exists.
|
||||
pub fn exist(path: &str) -> bool {
|
||||
fs::exist(path)
|
||||
}
|
||||
|
||||
/// Wrapper for fs::find_file
|
||||
///
|
||||
/// Find a file in a directory (with support for wildcards).
|
||||
pub fn find_file(dir: &str, filename: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::find_file(dir, filename).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::find_files
|
||||
///
|
||||
/// Find multiple files in a directory (recursive, with support for wildcards).
|
||||
pub fn find_files(dir: &str, filename: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||
let files = fs::find_files(dir, filename).to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for file in files {
|
||||
array.push(file.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
/// Wrapper for fs::find_dir
|
||||
///
|
||||
/// Find a directory in a parent directory (with support for wildcards).
|
||||
pub fn find_dir(dir: &str, dirname: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::find_dir(dir, dirname).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::find_dirs
|
||||
///
|
||||
/// Find multiple directories in a parent directory (recursive, with support for wildcards).
|
||||
pub fn find_dirs(dir: &str, dirname: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||
let dirs = fs::find_dirs(dir, dirname).to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for dir in dirs {
|
||||
array.push(dir.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
/// Wrapper for fs::delete
|
||||
///
|
||||
/// Delete a file or directory (defensive - doesn't error if file doesn't exist).
|
||||
pub fn delete(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::delete(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::mkdir
|
||||
///
|
||||
/// Create a directory and all parent directories (defensive - doesn't error if directory exists).
|
||||
pub fn mkdir(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::mkdir(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::file_size
|
||||
///
|
||||
/// Get the size of a file in bytes.
|
||||
pub fn file_size(path: &str) -> Result<i64, Box<EvalAltResult>> {
|
||||
fs::file_size(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::rsync
|
||||
///
|
||||
/// Sync directories using rsync (or platform equivalent).
|
||||
pub fn rsync(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::rsync(src, dest).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::chdir
|
||||
///
|
||||
/// Change the current working directory.
|
||||
pub fn chdir(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::chdir(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::file_read
|
||||
///
|
||||
/// Read the contents of a file.
|
||||
pub fn file_read(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::file_read(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::file_write
|
||||
///
|
||||
/// Write content to a file (creates the file if it doesn't exist, overwrites if it does).
|
||||
pub fn file_write(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::file_write(path, content).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::file_write_append
|
||||
///
|
||||
/// Append content to a file (creates the file if it doesn't exist).
|
||||
pub fn file_write_append(path: &str, content: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::file_write_append(path, content).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for fs::mv
|
||||
///
|
||||
/// Move a file or directory from source to destination.
|
||||
pub fn mv(src: &str, dest: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::mv(src, dest).to_rhai_error()
|
||||
}
|
||||
|
||||
//
|
||||
// Download Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for os::download
|
||||
///
|
||||
/// Download a file from URL to destination using the curl command.
|
||||
pub fn download(url: &str, dest: &str, min_size_kb: i64) -> Result<String, Box<EvalAltResult>> {
|
||||
dl::download(url, dest, min_size_kb).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::download_file
|
||||
///
|
||||
/// Download a file from URL to a specific file destination using the curl command.
|
||||
pub fn download_file(
|
||||
url: &str,
|
||||
dest: &str,
|
||||
min_size_kb: i64,
|
||||
) -> Result<String, Box<EvalAltResult>> {
|
||||
dl::download_file(url, dest, min_size_kb).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::download_install
|
||||
///
|
||||
/// Download a file and install it if it's a supported package format.
|
||||
pub fn download_install(url: &str, min_size_kb: i64) -> Result<String, Box<EvalAltResult>> {
|
||||
dl::download_install(url, min_size_kb).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::chmod_exec
|
||||
///
|
||||
/// Make a file executable (equivalent to chmod +x).
|
||||
pub fn chmod_exec(path: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
dl::chmod_exec(path).to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::which
|
||||
///
|
||||
/// Check if a command exists in the system PATH.
|
||||
pub fn which(command: &str) -> String {
|
||||
fs::which(command)
|
||||
}
|
||||
|
||||
/// Wrapper for os::cmd_ensure_exists
|
||||
///
|
||||
/// Ensure that one or more commands exist in the system PATH.
|
||||
/// If any command doesn't exist, an error is thrown.
|
||||
pub fn cmd_ensure_exists(commands: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
fs::cmd_ensure_exists(commands).to_rhai_error()
|
||||
}
|
||||
|
||||
//
|
||||
// Package Management Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for os::package::PackHero::install
|
||||
///
|
||||
/// Install a package using the system package manager.
|
||||
pub fn package_install(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.install(package)
|
||||
.map(|_| format!("Package '{}' installed successfully", package))
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::remove
|
||||
///
|
||||
/// Remove a package using the system package manager.
|
||||
pub fn package_remove(package: &str) -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.remove(package)
|
||||
.map(|_| format!("Package '{}' removed successfully", package))
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::update
|
||||
///
|
||||
/// Update package lists using the system package manager.
|
||||
pub fn package_update() -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.update()
|
||||
.map(|_| "Package lists updated successfully".to_string())
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::upgrade
|
||||
///
|
||||
/// Upgrade installed packages using the system package manager.
|
||||
pub fn package_upgrade() -> Result<String, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.upgrade()
|
||||
.map(|_| "Packages upgraded successfully".to_string())
|
||||
.to_rhai_error()
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::list_installed
|
||||
///
|
||||
/// List installed packages using the system package manager.
|
||||
pub fn package_list() -> Result<Array, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
let packages = hero.list_installed().to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for package in packages {
|
||||
array.push(package.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::search
|
||||
///
|
||||
/// Search for packages using the system package manager.
|
||||
pub fn package_search(query: &str) -> Result<Array, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
let packages = hero.search(query).to_rhai_error()?;
|
||||
|
||||
// Convert Vec<String> to Rhai Array
|
||||
let mut array = Array::new();
|
||||
for package in packages {
|
||||
array.push(package.into());
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
/// Wrapper for os::package::PackHero::is_installed
|
||||
///
|
||||
/// Check if a package is installed using the system package manager.
|
||||
pub fn package_is_installed(package: &str) -> Result<bool, Box<EvalAltResult>> {
|
||||
let hero = PackHero::new();
|
||||
hero.is_installed(package).to_rhai_error()
|
||||
}
|
||||
|
||||
// Thread-local storage for package debug flag
|
||||
thread_local! {
|
||||
static PACKAGE_DEBUG: std::cell::RefCell<bool> = std::cell::RefCell::new(false);
|
||||
}
|
||||
|
||||
/// Set the debug mode for package management operations
|
||||
pub fn package_set_debug(debug: bool) -> bool {
|
||||
let mut hero = PackHero::new();
|
||||
hero.set_debug(debug);
|
||||
|
||||
// Also set the thread-local debug flag
|
||||
PACKAGE_DEBUG.with(|cell| {
|
||||
*cell.borrow_mut() = debug;
|
||||
});
|
||||
|
||||
debug
|
||||
}
|
||||
|
||||
/// Get the current platform name for package management
|
||||
pub fn package_platform() -> String {
|
||||
let hero = PackHero::new();
|
||||
match hero.platform() {
|
||||
package::Platform::Ubuntu => "Ubuntu".to_string(),
|
||||
package::Platform::MacOS => "MacOS".to_string(),
|
||||
package::Platform::Unknown => "Unknown".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Platform Detection Function Wrappers
|
||||
//
|
||||
|
||||
/// Wrapper for platform::is_osx
|
||||
pub fn platform_is_osx() -> bool {
|
||||
crate::platform::is_osx()
|
||||
}
|
||||
|
||||
/// Wrapper for platform::is_linux
|
||||
pub fn platform_is_linux() -> bool {
|
||||
crate::platform::is_linux()
|
||||
}
|
||||
|
||||
/// Wrapper for platform::is_arm
|
||||
pub fn platform_is_arm() -> bool {
|
||||
crate::platform::is_arm()
|
||||
}
|
||||
|
||||
/// Wrapper for platform::is_x86
|
||||
pub fn platform_is_x86() -> bool {
|
||||
crate::platform::is_x86()
|
||||
}
|
||||
|
||||
/// Wrapper for platform::check_linux_x86
|
||||
pub fn platform_check_linux_x86() -> Result<(), Box<EvalAltResult>> {
|
||||
crate::platform::check_linux_x86().map_err(|e| {
|
||||
Box::new(EvalAltResult::ErrorRuntime(
|
||||
format!("Platform Check Error: {}", e).into(),
|
||||
Position::NONE,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Wrapper for platform::check_macos_arm
|
||||
pub fn platform_check_macos_arm() -> Result<(), Box<EvalAltResult>> {
|
||||
crate::platform::check_macos_arm().map_err(|e| {
|
||||
Box::new(EvalAltResult::ErrorRuntime(
|
||||
format!("Platform Check Error: {}", e).into(),
|
||||
Position::NONE,
|
||||
))
|
||||
})
|
||||
}
|
Reference in New Issue
Block a user