//! 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>> = Mutex::new(None); static ref RUNTIME: Mutex> = Mutex::new(None); } /// Wrapper around RfsClient to make it thread-safe for global usage struct RfsClientWrapper { client: Mutex, } 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>` - Ok if registration was successful, Err otherwise pub fn register_rfs_module(engine: &mut Engine) -> Result<(), Box> { // 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>, Box> { 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, Box> { 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> { 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>` - Ok(true) if client was created successfully pub fn rfs_create_client( base_url: &str, username: &str, password: &str, timeout_seconds: rhai::INT, ) -> Result> { 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>` - Ok(true) if authentication was successful pub fn rfs_authenticate() -> Result> { 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> { 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>` - System information as JSON string pub fn rfs_get_system_info() -> Result> { 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> { 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, per_page: Option, ) -> Result> { 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> { 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> { 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> { // Parse the JSON array of hashes let hashes_vec: Vec = 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 = 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> { 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> { 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, per_page: Option, ) -> Result> { 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> { 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 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>` - Ok(()) if download was successful, error otherwise fn rfs_download_file(file_id: &str, output_path: &str, verify: bool) -> Result<(), Box> { 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>` - File ID of the uploaded file pub fn rfs_upload_file(file_path: &str, chunk_size: rhai::INT, verify: bool) -> Result> { 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> { 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> { 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> { 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> { 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> { 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> { 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, poll_interval_ms: Option, ) -> Result> { 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, ))), } }