herolib_rust/rfs-client/src/rhai.rs

1167 lines
37 KiB
Rust

//! Rhai wrappers for RFS client module functions
//!
//! This module provides Rhai wrappers for the functions in the RFS client module.
use crate::client::RfsClient;
use crate::types::{ClientConfig, Credentials, DownloadOptions, UploadOptions, WaitOptions};
use crate::RfsError;
use lazy_static::lazy_static;
use rhai::{Dynamic, Engine, EvalAltResult, Map};
use serde_json::Value;
use std::sync::{Arc, Mutex};
use tokio::runtime::Runtime;
// Global RFS client and runtime management
lazy_static! {
static ref RFS_CLIENT: Mutex<Option<Arc<RfsClientWrapper>>> = Mutex::new(None);
static ref RUNTIME: Mutex<Option<Runtime>> = Mutex::new(None);
}
/// Wrapper around RfsClient to make it thread-safe for global usage
struct RfsClientWrapper {
client: Mutex<RfsClient>,
}
impl RfsClientWrapper {
fn new(client: RfsClient) -> Self {
Self { client: Mutex::new(client) }
}
}
/// Register RFS 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_rfs_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
// Register RFS client functions
engine.register_fn("rfs_create_client", rfs_create_client);
engine.register_fn("rfs_authenticate", rfs_authenticate);
engine.register_fn("rfs_get_system_info", rfs_get_system_info);
engine.register_fn("rfs_is_authenticated", rfs_is_authenticated);
engine.register_fn("rfs_health_check", rfs_health_check);
// Register block management functions
engine.register_fn("rfs_list_blocks", rfs_list_blocks);
engine.register_fn("rfs_upload_block", rfs_upload_block);
engine.register_fn("rfs_check_block", rfs_check_block);
engine.register_fn("rfs_get_block_downloads", rfs_get_block_downloads);
engine.register_fn("rfs_verify_blocks", rfs_verify_blocks);
engine.register_fn("rfs_get_block", rfs_get_block);
engine.register_fn("rfs_get_blocks_by_hash", rfs_get_blocks_by_hash);
engine.register_fn("rfs_get_user_blocks", rfs_get_user_blocks);
// Register file operations functions
engine.register_fn("rfs_upload_file", rfs_upload_file);
engine.register_fn("rfs_download_file", rfs_download_file);
// Register FList management functions
engine.register_fn("rfs_create_flist", rfs_create_flist);
engine.register_fn("rfs_list_flists", rfs_list_flists);
engine.register_fn("rfs_get_flist_state", rfs_get_flist_state);
engine.register_fn("rfs_preview_flist", rfs_preview_flist);
engine.register_fn("rfs_download_flist", rfs_download_flist);
engine.register_fn("rfs_wait_for_flist_creation", rfs_wait_for_flist_creation);
// Register Website functions
engine.register_fn("rfs_get_website", rfs_get_website);
// Register System and Utility functions
engine.register_fn("rfs_is_authenticated", rfs_is_authenticated);
engine.register_fn("rfs_health_check", rfs_health_check);
Ok(())
}
// Helper function to get or create the Tokio runtime
fn get_runtime() -> Result<&'static Mutex<Option<Runtime>>, Box<EvalAltResult>> {
let mut runtime = RUNTIME.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
if runtime.is_none() {
let rt = Runtime::new().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to create Tokio runtime: {}", e).into(),
rhai::Position::NONE,
))
})?;
*runtime = Some(rt);
}
drop(runtime);
Ok(&RUNTIME)
}
// Helper function to get the RFS client
fn get_rfs_client() -> Result<Arc<RfsClientWrapper>, Box<EvalAltResult>> {
let client_guard = RFS_CLIENT.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
match client_guard.as_ref() {
Some(client) => Ok(Arc::clone(client)),
None => Err(Box::new(EvalAltResult::ErrorRuntime(
"RFS client not initialized. Call rfs_create_client first.".into(),
rhai::Position::NONE,
))),
}
}
// Helper function to convert serde_json::Value to rhai::Dynamic
fn to_dynamic(value: Value) -> Dynamic {
match value {
Value::Null => Dynamic::UNIT,
Value::Bool(b) => Dynamic::from(b),
Value::Number(n) => {
if let Some(i) = n.as_i64() {
Dynamic::from(i)
} else if let Some(f) = n.as_f64() {
Dynamic::from(f)
} else {
Dynamic::from(n.to_string())
}
}
Value::String(s) => Dynamic::from(s),
Value::Array(arr) => {
let mut rhai_arr = rhai::Array::new();
for item in arr {
rhai_arr.push(to_dynamic(item));
}
Dynamic::from(rhai_arr)
}
Value::Object(map) => {
let mut rhai_map = Map::new();
for (k, v) in map {
rhai_map.insert(k.into(), to_dynamic(v));
}
Dynamic::from_map(rhai_map)
}
}
}
// Helper function to convert JSON string to Dynamic
fn json_to_dynamic(json_str: &str) -> Result<Dynamic, Box<EvalAltResult>> {
let value: Value = serde_json::from_str(json_str).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to parse JSON: {}", e).into(),
rhai::Position::NONE,
))
})?;
Ok(to_dynamic(value))
}
//
// RFS Client Function Wrappers
//
/// Create a new RFS client
///
/// # Arguments
///
/// * `base_url` - The base URL of the RFS server
/// * `username` - Username for authentication
/// * `password` - Password for authentication
/// * `timeout_seconds` - Request timeout in seconds (optional, defaults to 30)
///
/// # Returns
///
/// * `Result<bool, Box<EvalAltResult>>` - Ok(true) if client was created successfully
pub fn rfs_create_client(
base_url: &str,
username: &str,
password: &str,
timeout_seconds: rhai::INT,
) -> Result<bool, Box<EvalAltResult>> {
let credentials = if username.is_empty() || password.is_empty() {
None
} else {
Some(Credentials {
username: username.to_string(),
password: password.to_string(),
})
};
let client_config = ClientConfig {
base_url: base_url.to_string(),
credentials,
timeout_seconds: timeout_seconds as u64,
};
let client = RfsClient::new(client_config);
let wrapper = Arc::new(RfsClientWrapper::new(client));
let mut client_guard = RFS_CLIENT.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
*client_guard = Some(wrapper);
Ok(true)
}
/// Authenticate with the RFS server
///
/// # Returns
///
/// * `Result<bool, Box<EvalAltResult>>` - Ok(true) if authentication was successful
pub fn rfs_authenticate() -> Result<bool, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let mut client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async { client.authenticate().await });
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Authentication failed: {}", e).into(),
rhai::Position::NONE,
))
})?;
Ok(true)
}
/// Check if the client is authenticated with the RFS server
///
/// # Returns
/// `true` if authenticated, `false` otherwise
fn rfs_is_authenticated() -> Result<bool, Box<EvalAltResult>> {
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
Ok(client.is_authenticated())
}
/// Get system information from the RFS server
///
/// # Returns
///
/// * `Result<String, Box<EvalAltResult>>` - System information as JSON string
pub fn rfs_get_system_info() -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client = get_rfs_client()?;
let client_guard = client.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async { client_guard.get_system_info().await });
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("RFS error: {}", e).into(),
rhai::Position::NONE,
))
})
}
/// Check the health status of the RFS server
///
/// # Returns
/// The health status as a string
fn rfs_health_check() -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async {
client.health_check().await
});
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Health check failed: {}", e).into(),
rhai::Position::NONE,
))
})
}
// =============================================================================
// Block Management Functions
// =============================================================================
/// List all blocks with optional filtering
///
/// # Arguments
/// * `page` - Optional page number (1-based)
/// * `per_page` - Optional number of items per page
///
/// # Returns
/// JSON string containing block information
fn rfs_list_blocks(
page: Option<rhai::INT>,
per_page: Option<rhai::INT>,
) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
// Create ListBlocksParams with optional page and per_page
let mut params = openapi::models::ListBlocksParams::new();
// Convert Rhai INT to i32 for the API and set the parameters
if let Some(p) = page.and_then(|p| p.try_into().ok()) {
params.page = Some(Some(p));
}
if let Some(pp) = per_page.and_then(|p| p.try_into().ok()) {
params.per_page = Some(Some(pp));
}
let result = runtime.block_on(async {
client.list_blocks(Some(params)).await
});
match result {
Ok(blocks) => {
// Convert blocks to JSON string for Rhai
serde_json::to_string(&blocks).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to serialize blocks: {}", e).into(),
rhai::Position::NONE,
))
})
}
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to list blocks: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Check if a block exists
///
/// # Arguments
/// * `hash` - The hash of the block to check
///
/// # Returns
/// `true` if the block exists, `false` otherwise
fn rfs_check_block(hash: &str) -> Result<bool, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async {
client.check_block(hash).await
});
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to check block: {}", e).into(),
rhai::Position::NONE,
))
})
}
/// Get block download statistics
///
/// # Arguments
/// * `hash` - The hash of the block
///
/// # Returns
/// JSON string containing download statistics
fn rfs_get_block_downloads(hash: &str) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async {
client.get_block_downloads(hash).await
});
match result {
Ok(stats) => {
// Convert stats to JSON string for Rhai
serde_json::to_string(&stats).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to serialize block stats: {}", e).into(),
rhai::Position::NONE,
))
})
}
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get block downloads: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Verify blocks
///
/// # Arguments
/// * `hashes` - JSON array of block hashes to verify
///
/// # Returns
/// JSON string containing verification results
fn rfs_verify_blocks(hashes: &str) -> Result<String, Box<EvalAltResult>> {
// Parse the JSON array of hashes
let hashes_vec: Vec<String> = match serde_json::from_str(hashes) {
Ok(h) => h,
Err(e) => {
return Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to parse hashes: {}", e).into(),
rhai::Position::NONE,
)));
}
};
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
// Convert string hashes to VerifyBlock objects
// For now, we'll use the hash as both block_hash and file_hash, and use 0 as block_index
// In a real implementation, you might want to pass these as separate parameters
let verify_blocks: Vec<openapi::models::VerifyBlock> = hashes_vec
.into_iter()
.map(|block_hash| openapi::models::VerifyBlock {
block_hash: block_hash.clone(),
block_index: 0, // Default to 0 if not specified
file_hash: block_hash, // Using the same hash as file_hash for now
})
.collect();
let request = openapi::models::VerifyBlocksRequest::new(verify_blocks);
let result = runtime.block_on(async {
client.verify_blocks(request).await
});
match result {
Ok(verification) => {
// Convert verification to JSON string for Rhai
serde_json::to_string(&verification).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to serialize verification results: {}", e).into(),
rhai::Position::NONE,
))
})
}
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to verify blocks: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Get a block by hash
///
/// # Arguments
/// * `hash` - The hash of the block to retrieve
///
/// # Returns
/// The block data as a byte array
fn rfs_get_block(hash: &str) -> Result<rhai::Blob, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async {
client.get_block(hash).await
});
match result {
Ok(bytes) => Ok(bytes.to_vec().into()),
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get block: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Get blocks by file hash or block hash
///
/// # Arguments
/// * `hash` - The file hash or block hash to look up
///
/// # Returns
/// JSON string containing block information
fn rfs_get_blocks_by_hash(hash: &str) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async {
client.get_blocks_by_hash(hash).await
});
match result {
Ok(blocks) => {
// Convert blocks to JSON string for Rhai
serde_json::to_string(&blocks).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to serialize blocks: {}", e).into(),
rhai::Position::NONE,
))
})
}
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get blocks by hash: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Get blocks uploaded by the current user
///
/// # Arguments
/// * `page` - Optional page number (1-based)
/// * `per_page` - Optional number of items per page
///
/// # Returns
/// JSON string containing user's blocks information
fn rfs_get_user_blocks(
page: Option<rhai::INT>,
per_page: Option<rhai::INT>,
) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
// Convert Rhai INT to i32 for the API
let page_i32 = page.and_then(|p| p.try_into().ok());
let per_page_i32 = per_page.and_then(|p| p.try_into().ok());
let result = runtime.block_on(async {
client.get_user_blocks(page_i32, per_page_i32).await
});
match result {
Ok(user_blocks) => {
// Convert user blocks to JSON string for Rhai
serde_json::to_string(&user_blocks).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to serialize user blocks: {}", e).into(),
rhai::Position::NONE,
))
})
}
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get user blocks: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Upload a block to the RFS server
///
/// # Arguments
/// * `file_hash` - The hash of the file this block belongs to
/// * `index` - The index of the block in the file
/// * `data` - The block data as a byte array
///
/// # Returns
/// The hash of the uploaded block
fn rfs_upload_block(file_hash: &str, index: rhai::INT, data: rhai::Blob) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
// Convert index to i64 for the API
let index_i64 = index as i64;
// Convert the blob to Vec<u8>
let data_vec = data.to_vec();
let result = runtime.block_on(async {
client.upload_block(file_hash, index_i64, data_vec).await
});
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to upload block: {}", e).into(),
rhai::Position::NONE,
))
})
}
/// Upload a file to the RFS server
/// * `index` - The index of the block in the file
/// * `data` - The block data as a byte array
///
/// # Returns
/// The hash of the uploaded block
// =============================================================================
// File Operations
// =============================================================================
/// Download a file from the RFS server
///
/// # Arguments
///
/// * `file_id` - The ID of the file to download
/// * `output_path` - Path where the downloaded file will be saved
/// * `verify` - Whether to verify blocks during download
///
/// # Returns
///
/// * `Result<(), Box<EvalAltResult>>` - Ok(()) if download was successful, error otherwise
fn rfs_download_file(file_id: &str, output_path: &str, verify: bool) -> Result<(), Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let download_options = Some(DownloadOptions { verify });
let result = runtime.block_on(async {
client.download_file(file_id, output_path, download_options).await
});
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to download file: {}", e).into(),
rhai::Position::NONE,
))
})
}
/// Upload a file to the RFS server
///
/// # Arguments
///
/// * `file_path` - Path to the file to upload
/// * `chunk_size` - Optional chunk size for large files (0 for default)
/// * `verify` - Whether to verify blocks after upload
///
/// # Returns
///
/// * `Result<String, Box<EvalAltResult>>` - File ID of the uploaded file
pub fn rfs_upload_file(file_path: &str, chunk_size: rhai::INT, verify: bool) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let upload_options = Some(UploadOptions {
chunk_size: if chunk_size > 0 { Some(chunk_size as usize) } else { None },
verify,
});
let result = runtime.block_on(async { client.upload_file(file_path, upload_options).await });
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("RFS error: {}", e).into(),
rhai::Position::NONE,
))
})
}
// =============================================================================
// Website Functions
// =============================================================================
/// Get website content from the RFS server
///
/// # Arguments
/// * `website_id` - The ID of the website
/// * `path` - The path to the content within the website
///
/// # Returns
/// The website content as a string
fn rfs_get_website(website_id: &str, path: &str) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async {
let response = client.get_website(website_id, path).await?;
response.text().await.map_err(|e| RfsError::RequestError(e.into()))
});
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get website content: {}", e).into(),
rhai::Position::NONE,
))
})
}
// =============================================================================
// FList Management Functions
// =============================================================================
/// Create an FList from a Docker image
///
/// # Arguments
/// * `image_name` - Docker image name (e.g., "ubuntu:20.04")
/// * `server_address` - Optional server address (empty string if not needed)
/// * `identity_token` - Optional identity token (empty string if not needed)
/// * `registry_token` - Optional registry token (empty string if not needed)
///
/// # Returns
/// Job ID for tracking FList creation progress
fn rfs_create_flist(
image_name: &str,
server_address: &str,
identity_token: &str,
registry_token: &str,
) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
// Build FList options
let mut options = crate::types::FlistOptions::default();
if !server_address.is_empty() {
options.server_address = Some(server_address.to_string());
}
if !identity_token.is_empty() {
options.identity_token = Some(identity_token.to_string());
}
if !registry_token.is_empty() {
options.registry_token = Some(registry_token.to_string());
}
let result = runtime.block_on(async { client.create_flist(image_name, Some(options)).await });
result.map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("FList creation failed: {}", e).into(),
rhai::Position::NONE,
))
})
}
/// List all available FLists
///
/// # Returns
/// JSON string containing FList information
fn rfs_list_flists() -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async { client.list_flists().await });
match result {
Ok(flists) => {
// Convert HashMap to JSON string for Rhai
serde_json::to_string(&flists).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to serialize FList data: {}", e).into(),
rhai::Position::NONE,
))
})
}
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to list FLists: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Get FList creation state by job ID
///
/// # Arguments
/// * `job_id` - Job ID returned from create_flist
///
/// # Returns
/// JSON string containing FList state information
fn rfs_get_flist_state(job_id: &str) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async { client.get_flist_state(job_id).await });
match result {
Ok(state) => {
// Convert state to JSON string for Rhai
serde_json::to_string(&state).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to serialize FList state: {}", e).into(),
rhai::Position::NONE,
))
})
}
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to get FList state: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Preview an FList's contents
///
/// # Arguments
/// * `flist_path` - Path to the FList
///
/// # Returns
/// JSON string containing FList preview information
fn rfs_preview_flist(flist_path: &str) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async { client.preview_flist(flist_path).await });
match result {
Ok(preview) => {
// Convert preview to JSON string for Rhai
serde_json::to_string(&preview).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to serialize FList preview: {}", e).into(),
rhai::Position::NONE,
))
})
}
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to preview FList: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Download an FList file from the RFS server
///
/// # Arguments
/// * `flist_path` - Path to the FList to download (e.g., "flists/user/example.fl")
/// * `output_path` - Local path where the FList will be saved
///
/// # Returns
/// Empty string on success, error on failure
fn rfs_download_flist(flist_path: &str, output_path: &str) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let result = runtime.block_on(async {
client.download_flist(flist_path, output_path).await
});
match result {
Ok(_) => Ok(String::new()),
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to download FList: {}", e).into(),
rhai::Position::NONE,
))),
}
}
/// Wait for an FList to be created
///
/// # Arguments
/// * `job_id` - The job ID returned by rfs_create_flist
/// * `timeout_seconds` - Maximum time to wait in seconds (default: 300)
/// * `poll_interval_ms` - Polling interval in milliseconds (default: 1000)
///
/// # Returns
/// JSON string containing the final FList state
fn rfs_wait_for_flist_creation(
job_id: &str,
timeout_seconds: Option<rhai::INT>,
poll_interval_ms: Option<rhai::INT>,
) -> Result<String, Box<EvalAltResult>> {
let runtime_mutex = get_runtime()?;
let runtime_guard = runtime_mutex.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock runtime mutex: {}", e).into(),
rhai::Position::NONE,
))
})?;
let runtime = runtime_guard.as_ref().unwrap();
let client_wrapper = get_rfs_client()?;
let client = client_wrapper.client.lock().map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to lock client: {}", e).into(),
rhai::Position::NONE,
))
})?;
let options = WaitOptions {
timeout_seconds: timeout_seconds.unwrap_or(300) as u64,
poll_interval_ms: poll_interval_ms.unwrap_or(1000) as u64,
progress_callback: None,
};
let result = runtime.block_on(async {
client.wait_for_flist_creation(job_id, Some(options)).await
});
match result {
Ok(state) => {
// Convert state to JSON string for Rhai
serde_json::to_string(&state).map_err(|e| {
Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to serialize FList state: {}", e).into(),
rhai::Position::NONE,
))
})
}
Err(e) => Err(Box::new(EvalAltResult::ErrorRuntime(
format!("Failed to wait for FList creation: {}", e).into(),
rhai::Position::NONE,
))),
}
}