Compare commits
	
		
			10 Commits
		
	
	
		
			network_se
			...
			developmen
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 9865e601d7 | ||
|  | 7afa5ea1c0 | ||
|  | 6c2d96c9a5 | ||
|  | b2fc0976bd | ||
|  | e114404ca7 | ||
|  | 536779f521 | ||
|  | c2969621b1 | ||
|  | b39f24ca8f | ||
| f87a1d7f80 | |||
| 17e5924e0b | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -64,3 +64,6 @@ sidebars.ts | |||||||
| tsconfig.json | tsconfig.json | ||||||
| Cargo.toml.bak | Cargo.toml.bak | ||||||
| for_augment | for_augment | ||||||
|  |  | ||||||
|  | myenv.sh | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,6 +16,7 @@ members = [ | |||||||
|     "packages/clients/postgresclient", |     "packages/clients/postgresclient", | ||||||
|     "packages/clients/redisclient", |     "packages/clients/redisclient", | ||||||
|     "packages/clients/zinitclient", |     "packages/clients/zinitclient", | ||||||
|  |     "packages/clients/rfsclient", | ||||||
|     "packages/core/net", |     "packages/core/net", | ||||||
|     "packages/core/text", |     "packages/core/text", | ||||||
|     "packages/crypt/vault", |     "packages/crypt/vault", | ||||||
| @@ -31,6 +32,7 @@ members = [ | |||||||
|     "rhailib", |     "rhailib", | ||||||
|     "herodo", |     "herodo", | ||||||
|     "packages/clients/hetznerclient", |     "packages/clients/hetznerclient", | ||||||
|  |     "packages/ai/codemonkey", | ||||||
| ] | ] | ||||||
| resolver = "2" | resolver = "2" | ||||||
|  |  | ||||||
| @@ -42,6 +44,7 @@ rust-version = "1.70.0" | |||||||
| # Core shared dependencies with consistent versions | # Core shared dependencies with consistent versions | ||||||
| anyhow = "1.0.98" | anyhow = "1.0.98" | ||||||
| base64 = "0.22.1" | base64 = "0.22.1" | ||||||
|  | bytes = "1.7.1" | ||||||
| dirs = "6.0.0" | dirs = "6.0.0" | ||||||
| env_logger = "0.11.8" | env_logger = "0.11.8" | ||||||
| futures = "0.3.30" | futures = "0.3.30" | ||||||
| @@ -108,6 +111,7 @@ sal-kubernetes = { path = "packages/system/kubernetes" } | |||||||
| sal-redisclient = { path = "packages/clients/redisclient" } | sal-redisclient = { path = "packages/clients/redisclient" } | ||||||
| sal-mycelium = { path = "packages/clients/myceliumclient" } | sal-mycelium = { path = "packages/clients/myceliumclient" } | ||||||
| sal-hetzner = { path = "packages/clients/hetznerclient" } | sal-hetzner = { path = "packages/clients/hetznerclient" } | ||||||
|  | sal-rfs-client = { path = "packages/clients/rfsclient" } | ||||||
| sal-text = { path = "packages/core/text" } | sal-text = { path = "packages/core/text" } | ||||||
| sal-os = { path = "packages/system/os" } | sal-os = { path = "packages/system/os" } | ||||||
| sal-net = { path = "packages/core/net" } | sal-net = { path = "packages/core/net" } | ||||||
| @@ -129,6 +133,7 @@ sal-kubernetes = { workspace = true, optional = true } | |||||||
| sal-redisclient = { workspace = true, optional = true } | sal-redisclient = { workspace = true, optional = true } | ||||||
| sal-mycelium = { workspace = true, optional = true } | sal-mycelium = { workspace = true, optional = true } | ||||||
| sal-hetzner = { workspace = true, optional = true } | sal-hetzner = { workspace = true, optional = true } | ||||||
|  | sal-rfs-client = { workspace = true, optional = true } | ||||||
| sal-text = { workspace = true, optional = true } | sal-text = { workspace = true, optional = true } | ||||||
| sal-os = { workspace = true, optional = true } | sal-os = { workspace = true, optional = true } | ||||||
| sal-net = { workspace = true, optional = true } | sal-net = { workspace = true, optional = true } | ||||||
| @@ -149,6 +154,7 @@ kubernetes = ["dep:sal-kubernetes"] | |||||||
| redisclient = ["dep:sal-redisclient"] | redisclient = ["dep:sal-redisclient"] | ||||||
| mycelium = ["dep:sal-mycelium"] | mycelium = ["dep:sal-mycelium"] | ||||||
| hetzner = ["dep:sal-hetzner"] | hetzner = ["dep:sal-hetzner"] | ||||||
|  | rfsclient = ["dep:sal-rfs-client"] | ||||||
| text = ["dep:sal-text"] | text = ["dep:sal-text"] | ||||||
| os = ["dep:sal-os"] | os = ["dep:sal-os"] | ||||||
| net = ["dep:sal-net"] | net = ["dep:sal-net"] | ||||||
| @@ -162,7 +168,7 @@ rhai = ["dep:sal-rhai"] | |||||||
|  |  | ||||||
| # Convenience feature groups | # Convenience feature groups | ||||||
| core = ["os", "process", "text", "net"] | core = ["os", "process", "text", "net"] | ||||||
| clients = ["redisclient", "postgresclient", "zinit_client", "mycelium", "hetzner"] | clients = ["redisclient", "postgresclient", "zinit_client", "mycelium", "hetzner", "rfsclient"] | ||||||
| infrastructure = ["git", "vault", "kubernetes", "virt"] | infrastructure = ["git", "vault", "kubernetes", "virt"] | ||||||
| scripting = ["rhai"] | scripting = ["rhai"] | ||||||
| all = [ | all = [ | ||||||
| @@ -171,6 +177,7 @@ all = [ | |||||||
|     "redisclient", |     "redisclient", | ||||||
|     "mycelium", |     "mycelium", | ||||||
|     "hetzner", |     "hetzner", | ||||||
|  |     "rfsclient", | ||||||
|     "text", |     "text", | ||||||
|     "os", |     "os", | ||||||
|     "net", |     "net", | ||||||
|   | |||||||
| @@ -1,43 +0,0 @@ | |||||||
| [package] |  | ||||||
| name = "sal-service-manager" |  | ||||||
| version = "0.1.0" |  | ||||||
| edition = "2021" |  | ||||||
| authors = ["PlanetFirst <info@incubaid.com>"] |  | ||||||
| description = "SAL Service Manager - Cross-platform service management for dynamic worker deployment" |  | ||||||
| repository = "https://git.threefold.info/herocode/sal" |  | ||||||
| license = "Apache-2.0" |  | ||||||
|  |  | ||||||
| [dependencies] |  | ||||||
| # Use workspace dependencies for consistency |  | ||||||
| thiserror = "1.0" |  | ||||||
| tokio = { workspace = true } |  | ||||||
| log = { workspace = true } |  | ||||||
| serde = { workspace = true } |  | ||||||
| serde_json = { workspace = true } |  | ||||||
| futures = { workspace = true } |  | ||||||
| once_cell = { workspace = true } |  | ||||||
| # Use base zinit-client instead of SAL wrapper |  | ||||||
| zinit-client = { version = "0.4.0" } |  | ||||||
| # Optional Rhai integration |  | ||||||
| rhai = { workspace = true, optional = true } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| [target.'cfg(target_os = "macos")'.dependencies] |  | ||||||
| # macOS-specific dependencies for launchctl |  | ||||||
| plist = "1.6" |  | ||||||
|  |  | ||||||
| [features] |  | ||||||
| default = ["zinit"] |  | ||||||
| zinit = [] |  | ||||||
| rhai = ["dep:rhai"] |  | ||||||
|  |  | ||||||
| # Enable zinit feature for tests |  | ||||||
| [dev-dependencies] |  | ||||||
| tokio-test = "0.4" |  | ||||||
| rhai = { workspace = true } |  | ||||||
| tempfile = { workspace = true } |  | ||||||
| env_logger = "0.10" |  | ||||||
|  |  | ||||||
| [[test]] |  | ||||||
| name = "zinit_integration_tests" |  | ||||||
| required-features = ["zinit"] |  | ||||||
| @@ -1,198 +0,0 @@ | |||||||
| # SAL Service Manager |  | ||||||
|  |  | ||||||
| [](https://crates.io/crates/sal-service-manager) |  | ||||||
| [](https://docs.rs/sal-service-manager) |  | ||||||
|  |  | ||||||
| A cross-platform service management library for the System Abstraction Layer (SAL). This crate provides a unified interface for managing system services across different platforms, enabling dynamic deployment of workers and services. |  | ||||||
|  |  | ||||||
| ## Features |  | ||||||
|  |  | ||||||
| - **Cross-platform service management** - Unified API across macOS and Linux |  | ||||||
| - **Dynamic worker deployment** - Perfect for circle workers and on-demand services |  | ||||||
| - **Platform-specific implementations**: |  | ||||||
|   - **macOS**: Uses `launchctl` with plist management |  | ||||||
|   - **Linux**: Uses `zinit` for lightweight service management (systemd also available) |  | ||||||
| - **Complete lifecycle management** - Start, stop, restart, status monitoring, and log retrieval |  | ||||||
| - **Service configuration** - Environment variables, working directories, auto-restart |  | ||||||
| - **Production-ready** - Comprehensive error handling and resource management |  | ||||||
|  |  | ||||||
| ## Usage |  | ||||||
|  |  | ||||||
| Add this to your `Cargo.toml`: |  | ||||||
|  |  | ||||||
| ```toml |  | ||||||
| [dependencies] |  | ||||||
| sal-service-manager = "0.1.0" |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Or use it as part of the SAL ecosystem: |  | ||||||
|  |  | ||||||
| ```toml |  | ||||||
| [dependencies] |  | ||||||
| sal = { version = "0.1.0", features = ["service_manager"] } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Primary Use Case: Dynamic Circle Worker Management |  | ||||||
|  |  | ||||||
| This service manager was designed specifically for dynamic deployment of circle workers in freezone environments. When a new resident registers, you can instantly launch a dedicated circle worker: |  | ||||||
|  |  | ||||||
| ```rust,no_run |  | ||||||
| use sal_service_manager::{create_service_manager, ServiceConfig}; |  | ||||||
| use std::collections::HashMap; |  | ||||||
|  |  | ||||||
| // New resident registration triggers worker creation |  | ||||||
| fn deploy_circle_worker(resident_id: &str) -> Result<(), Box<dyn std::error::Error>> { |  | ||||||
|     let manager = create_service_manager(); |  | ||||||
|  |  | ||||||
|     let mut env = HashMap::new(); |  | ||||||
|     env.insert("RESIDENT_ID".to_string(), resident_id.to_string()); |  | ||||||
|     env.insert("WORKER_TYPE".to_string(), "circle".to_string()); |  | ||||||
|  |  | ||||||
|     let config = ServiceConfig { |  | ||||||
|         name: format!("circle-worker-{}", resident_id), |  | ||||||
|         binary_path: "/usr/bin/circle-worker".to_string(), |  | ||||||
|         args: vec!["--resident".to_string(), resident_id.to_string()], |  | ||||||
|         working_directory: Some("/var/lib/circle-workers".to_string()), |  | ||||||
|         environment: env, |  | ||||||
|         auto_restart: true, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     // Deploy the worker |  | ||||||
|     manager.start(&config)?; |  | ||||||
|     println!("✅ Circle worker deployed for resident: {}", resident_id); |  | ||||||
|  |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Basic Usage Example |  | ||||||
|  |  | ||||||
| Here is an example of the core service management API: |  | ||||||
|  |  | ||||||
| ```rust,no_run |  | ||||||
| use sal_service_manager::{create_service_manager, ServiceConfig}; |  | ||||||
| use std::collections::HashMap; |  | ||||||
|  |  | ||||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { |  | ||||||
|     let service_manager = create_service_manager(); |  | ||||||
|  |  | ||||||
|     let config = ServiceConfig { |  | ||||||
|         name: "my-service".to_string(), |  | ||||||
|         binary_path: "/usr/local/bin/my-service-executable".to_string(), |  | ||||||
|         args: vec!["--config".to_string(), "/etc/my-service.conf".to_string()], |  | ||||||
|         working_directory: Some("/var/tmp".to_string()), |  | ||||||
|         environment: HashMap::new(), |  | ||||||
|         auto_restart: true, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     // Start a new service |  | ||||||
|     service_manager.start(&config)?; |  | ||||||
|  |  | ||||||
|     // Get the status of the service |  | ||||||
|     let status = service_manager.status("my-service")?; |  | ||||||
|     println!("Service status: {:?}", status); |  | ||||||
|  |  | ||||||
|     // Stop the service |  | ||||||
|     service_manager.stop("my-service")?; |  | ||||||
|  |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Examples |  | ||||||
|  |  | ||||||
| Comprehensive examples are available in the SAL examples directory: |  | ||||||
|  |  | ||||||
| ### Circle Worker Manager Example |  | ||||||
|  |  | ||||||
| The primary use case - dynamically launching circle workers for new freezone residents: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| # Run the circle worker management example |  | ||||||
| herodo examples/service_manager/circle_worker_manager.rhai |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| This example demonstrates: |  | ||||||
| - Creating service configurations for circle workers |  | ||||||
| - Complete service lifecycle management |  | ||||||
| - Error handling and status monitoring |  | ||||||
| - Service cleanup and removal |  | ||||||
|  |  | ||||||
| ### Basic Usage Example |  | ||||||
|  |  | ||||||
| A simpler example showing the core API: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| # Run the basic usage example |  | ||||||
| herodo examples/service_manager/basic_usage.rhai |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| See `examples/service_manager/README.md` for detailed documentation. |  | ||||||
|  |  | ||||||
| ## Testing |  | ||||||
|  |  | ||||||
| Run the test suite: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| cargo test -p sal-service-manager |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| For Rhai integration tests: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| cargo test -p sal-service-manager --features rhai |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Testing with Herodo |  | ||||||
|  |  | ||||||
| To test the service manager with real Rhai scripts using herodo, first build herodo: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| ./build_herodo.sh |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Then run Rhai scripts that use the service manager: |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| herodo your_service_script.rhai |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Prerequisites |  | ||||||
|  |  | ||||||
| ### Linux (zinit/systemd) |  | ||||||
|  |  | ||||||
| The service manager automatically discovers running zinit servers and falls back to systemd if none are found. |  | ||||||
|  |  | ||||||
| **For zinit (recommended):** |  | ||||||
|  |  | ||||||
| ```bash |  | ||||||
| # Start zinit with default socket |  | ||||||
| zinit -s /tmp/zinit.sock init |  | ||||||
|  |  | ||||||
| # Or with a custom socket path |  | ||||||
| zinit -s /var/run/zinit.sock init |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| **Socket Discovery:** |  | ||||||
| The service manager will automatically find running zinit servers by checking: |  | ||||||
| 1. `ZINIT_SOCKET_PATH` environment variable (if set) |  | ||||||
| 2. Common socket locations: `/var/run/zinit.sock`, `/tmp/zinit.sock`, `/run/zinit.sock`, `./zinit.sock` |  | ||||||
|  |  | ||||||
| **Custom socket path:** |  | ||||||
| ```bash |  | ||||||
| # Set custom socket path |  | ||||||
| export ZINIT_SOCKET_PATH=/your/custom/path/zinit.sock |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| **Systemd fallback:** |  | ||||||
| If no zinit server is detected, the service manager automatically falls back to systemd. |  | ||||||
|  |  | ||||||
| ### macOS (launchctl) |  | ||||||
|  |  | ||||||
| No additional setup required - uses the built-in launchctl system. |  | ||||||
|  |  | ||||||
| ## Platform Support |  | ||||||
|  |  | ||||||
| - **macOS**: Full support using `launchctl` for service management |  | ||||||
| - **Linux**: Full support using `zinit` for service management (systemd also available as alternative) |  | ||||||
| - **Windows**: Not currently supported |  | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| # Service Manager Examples |  | ||||||
|  |  | ||||||
| This directory contains examples demonstrating the usage of the `sal-service-manager` crate. |  | ||||||
|  |  | ||||||
| ## Running Examples |  | ||||||
|  |  | ||||||
| To run any example, use the following command structure from the `service_manager` crate's root directory: |  | ||||||
|  |  | ||||||
| ```sh |  | ||||||
| cargo run --example <EXAMPLE_NAME> |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| --- |  | ||||||
|  |  | ||||||
| ### 1. `simple_service` |  | ||||||
|  |  | ||||||
| This example demonstrates the ideal, clean lifecycle of a service using the separated `create` and `start` steps. |  | ||||||
|  |  | ||||||
| **Behavior:** |  | ||||||
| 1.  Creates a new service definition. |  | ||||||
| 2.  Starts the newly created service. |  | ||||||
| 3.  Checks its status to confirm it's running. |  | ||||||
| 4.  Stops the service. |  | ||||||
| 5.  Checks its status again to confirm it's stopped. |  | ||||||
| 6.  Removes the service definition. |  | ||||||
|  |  | ||||||
| **Run it:** |  | ||||||
| ```sh |  | ||||||
| cargo run --example simple_service |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### 2. `service_spaghetti` |  | ||||||
|  |  | ||||||
| This example demonstrates how the service manager handles "messy" or improper sequences of operations, showcasing its error handling and robustness. |  | ||||||
|  |  | ||||||
| **Behavior:** |  | ||||||
| 1.  Creates a service. |  | ||||||
| 2.  Starts the service. |  | ||||||
| 3.  Tries to start the **same service again** (which should fail as it's already running). |  | ||||||
| 4.  Removes the service **without stopping it first** (the manager should handle this gracefully). |  | ||||||
| 5.  Tries to stop the **already removed** service (which should fail). |  | ||||||
| 6.  Tries to remove the service **again** (which should also fail). |  | ||||||
|  |  | ||||||
| **Run it:** |  | ||||||
| ```sh |  | ||||||
| cargo run --example service_spaghetti |  | ||||||
| ``` |  | ||||||
| @@ -1,109 +0,0 @@ | |||||||
| //! service_spaghetti - An example of messy service management. |  | ||||||
| //! |  | ||||||
| //! This example demonstrates how the service manager behaves when commands |  | ||||||
| //! are issued in a less-than-ideal order, such as starting a service that's |  | ||||||
| //! already running or removing a service that hasn't been stopped. |  | ||||||
|  |  | ||||||
| use sal_service_manager::{create_service_manager, ServiceConfig}; |  | ||||||
| use std::collections::HashMap; |  | ||||||
| use std::thread; |  | ||||||
| use std::time::Duration; |  | ||||||
|  |  | ||||||
| fn main() { |  | ||||||
|     // Initialize logging to see socket discovery in action |  | ||||||
|     env_logger::init(); |  | ||||||
|  |  | ||||||
|     let manager = match create_service_manager() { |  | ||||||
|         Ok(manager) => manager, |  | ||||||
|         Err(e) => { |  | ||||||
|             eprintln!("Error: Failed to create service manager: {}", e); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|     let service_name = "com.herocode.examples.spaghetti"; |  | ||||||
|  |  | ||||||
|     let service_config = ServiceConfig { |  | ||||||
|         name: service_name.to_string(), |  | ||||||
|         binary_path: "/bin/sh".to_string(), |  | ||||||
|         args: vec![ |  | ||||||
|             "-c".to_string(), |  | ||||||
|             "while true; do echo 'Spaghetti service is running...'; sleep 5; done".to_string(), |  | ||||||
|         ], |  | ||||||
|         working_directory: None, |  | ||||||
|         environment: HashMap::new(), |  | ||||||
|         auto_restart: false, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     println!("--- Service Spaghetti Example ---"); |  | ||||||
|     println!("This example demonstrates messy, error-prone service management."); |  | ||||||
|  |  | ||||||
|     // Cleanup from previous runs to ensure a clean slate |  | ||||||
|     if let Ok(true) = manager.exists(service_name) { |  | ||||||
|         println!( |  | ||||||
|             "\nService '{}' found from a previous run. Cleaning up first.", |  | ||||||
|             service_name |  | ||||||
|         ); |  | ||||||
|         let _ = manager.stop(service_name); |  | ||||||
|         let _ = manager.remove(service_name); |  | ||||||
|         println!("Cleanup complete."); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 1. Start the service (creates and starts in one step) |  | ||||||
|     println!("\n1. Starting the service for the first time..."); |  | ||||||
|     match manager.start(&service_config) { |  | ||||||
|         Ok(()) => println!("   -> Success: Service '{}' started.", service_name), |  | ||||||
|         Err(e) => { |  | ||||||
|             eprintln!( |  | ||||||
|                 "   -> Error: Failed to start service: {}. Halting example.", |  | ||||||
|                 e |  | ||||||
|             ); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     thread::sleep(Duration::from_secs(2)); |  | ||||||
|  |  | ||||||
|     // 2. Try to start the service again while it's already running |  | ||||||
|     println!("\n2. Trying to start the *same service* again..."); |  | ||||||
|     match manager.start(&service_config) { |  | ||||||
|         Ok(()) => println!("   -> Unexpected Success: Service started again."), |  | ||||||
|         Err(e) => eprintln!( |  | ||||||
|             "   -> Expected Error: {}. The manager should detect it is already running.", |  | ||||||
|             e |  | ||||||
|         ), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 3. Let it run for a bit |  | ||||||
|     println!("\n3. Letting the service run for 5 seconds..."); |  | ||||||
|     thread::sleep(Duration::from_secs(5)); |  | ||||||
|  |  | ||||||
|     // 4. Remove the service without stopping it first |  | ||||||
|     // The `remove` function is designed to stop the service if it's running. |  | ||||||
|     println!("\n4. Removing the service without explicitly stopping it first..."); |  | ||||||
|     match manager.remove(service_name) { |  | ||||||
|         Ok(()) => println!("   -> Success: Service was stopped and removed."), |  | ||||||
|         Err(e) => eprintln!("   -> Error: Failed to remove service: {}", e), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 5. Try to stop the service after it has been removed |  | ||||||
|     println!("\n5. Trying to stop the service that was just removed..."); |  | ||||||
|     match manager.stop(service_name) { |  | ||||||
|         Ok(()) => println!("   -> Unexpected Success: Stopped a removed service."), |  | ||||||
|         Err(e) => eprintln!( |  | ||||||
|             "   -> Expected Error: {}. The manager knows the service is gone.", |  | ||||||
|             e |  | ||||||
|         ), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 6. Try to remove the service again |  | ||||||
|     println!("\n6. Trying to remove the service again..."); |  | ||||||
|     match manager.remove(service_name) { |  | ||||||
|         Ok(()) => println!("   -> Unexpected Success: Removed a non-existent service."), |  | ||||||
|         Err(e) => eprintln!( |  | ||||||
|             "   -> Expected Error: {}. The manager correctly reports it's not found.", |  | ||||||
|             e |  | ||||||
|         ), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     println!("\n--- Spaghetti Example Finished ---"); |  | ||||||
| } |  | ||||||
| @@ -1,110 +0,0 @@ | |||||||
| use sal_service_manager::{create_service_manager, ServiceConfig}; |  | ||||||
| use std::collections::HashMap; |  | ||||||
| use std::thread; |  | ||||||
| use std::time::Duration; |  | ||||||
|  |  | ||||||
| fn main() { |  | ||||||
|     // Initialize logging to see socket discovery in action |  | ||||||
|     env_logger::init(); |  | ||||||
|  |  | ||||||
|     // 1. Create a service manager for the current platform |  | ||||||
|     let manager = match create_service_manager() { |  | ||||||
|         Ok(manager) => manager, |  | ||||||
|         Err(e) => { |  | ||||||
|             eprintln!("Error: Failed to create service manager: {}", e); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     // 2. Define the configuration for our new service |  | ||||||
|     let service_name = "com.herocode.examples.simpleservice"; |  | ||||||
|     let service_config = ServiceConfig { |  | ||||||
|         name: service_name.to_string(), |  | ||||||
|         // A simple command that runs in a loop |  | ||||||
|         binary_path: "/bin/sh".to_string(), |  | ||||||
|         args: vec![ |  | ||||||
|             "-c".to_string(), |  | ||||||
|             "while true; do echo 'Simple service is running...'; date; sleep 5; done".to_string(), |  | ||||||
|         ], |  | ||||||
|         working_directory: None, |  | ||||||
|         environment: HashMap::new(), |  | ||||||
|         auto_restart: false, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     println!("--- Service Manager Example ---"); |  | ||||||
|  |  | ||||||
|     // Cleanup from previous runs, if necessary |  | ||||||
|     if let Ok(true) = manager.exists(service_name) { |  | ||||||
|         println!( |  | ||||||
|             "Service '{}' already exists. Cleaning up before starting.", |  | ||||||
|             service_name |  | ||||||
|         ); |  | ||||||
|         if let Err(e) = manager.stop(service_name) { |  | ||||||
|             println!( |  | ||||||
|                 "Note: could not stop existing service (it might not be running): {}", |  | ||||||
|                 e |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|         if let Err(e) = manager.remove(service_name) { |  | ||||||
|             eprintln!("Error: failed to remove existing service: {}", e); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         println!("Cleanup complete."); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 3. Start the service (creates and starts in one step) |  | ||||||
|     println!("\n1. Starting service: '{}'", service_name); |  | ||||||
|     match manager.start(&service_config) { |  | ||||||
|         Ok(()) => println!("Service '{}' started successfully.", service_name), |  | ||||||
|         Err(e) => { |  | ||||||
|             eprintln!("Error: Failed to start service '{}': {}", service_name, e); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Give it a moment to run |  | ||||||
|     println!("\nWaiting for 2 seconds for the service to initialize..."); |  | ||||||
|     thread::sleep(Duration::from_secs(2)); |  | ||||||
|  |  | ||||||
|     // 4. Check the status of the service |  | ||||||
|     println!("\n2. Checking service status..."); |  | ||||||
|     match manager.status(service_name) { |  | ||||||
|         Ok(status) => println!("Service status: {:?}", status), |  | ||||||
|         Err(e) => eprintln!( |  | ||||||
|             "Error: Failed to get status for service '{}': {}", |  | ||||||
|             service_name, e |  | ||||||
|         ), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     println!("\nLetting the service run for 10 seconds. Check logs if you can."); |  | ||||||
|     thread::sleep(Duration::from_secs(10)); |  | ||||||
|  |  | ||||||
|     // 5. Stop the service |  | ||||||
|     println!("\n3. Stopping service: '{}'", service_name); |  | ||||||
|     match manager.stop(service_name) { |  | ||||||
|         Ok(()) => println!("Service '{}' stopped successfully.", service_name), |  | ||||||
|         Err(e) => eprintln!("Error: Failed to stop service '{}': {}", service_name, e), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     println!("\nWaiting for 2 seconds for the service to stop..."); |  | ||||||
|     thread::sleep(Duration::from_secs(2)); |  | ||||||
|  |  | ||||||
|     // Check status again |  | ||||||
|     println!("\n4. Checking status after stopping..."); |  | ||||||
|     match manager.status(service_name) { |  | ||||||
|         Ok(status) => println!("Service status: {:?}", status), |  | ||||||
|         Err(e) => eprintln!( |  | ||||||
|             "Error: Failed to get status for service '{}': {}", |  | ||||||
|             service_name, e |  | ||||||
|         ), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // 6. Remove the service |  | ||||||
|     println!("\n5. Removing service: '{}'", service_name); |  | ||||||
|     match manager.remove(service_name) { |  | ||||||
|         Ok(()) => println!("Service '{}' removed successfully.", service_name), |  | ||||||
|         Err(e) => eprintln!("Error: Failed to remove service '{}': {}", service_name, e), |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     println!("\n--- Example Finished ---"); |  | ||||||
| } |  | ||||||
| @@ -1,47 +0,0 @@ | |||||||
| //! Socket Discovery Test |  | ||||||
| //! |  | ||||||
| //! This example demonstrates the zinit socket discovery functionality. |  | ||||||
| //! It shows how the service manager finds available zinit sockets. |  | ||||||
|  |  | ||||||
| use sal_service_manager::create_service_manager; |  | ||||||
|  |  | ||||||
| fn main() { |  | ||||||
|     // Initialize logging to see socket discovery in action |  | ||||||
|     env_logger::init(); |  | ||||||
|      |  | ||||||
|     println!("=== Zinit Socket Discovery Test ==="); |  | ||||||
|     println!("This test demonstrates how the service manager discovers zinit sockets."); |  | ||||||
|     println!(); |  | ||||||
|      |  | ||||||
|     // Test environment variable |  | ||||||
|     if let Ok(socket_path) = std::env::var("ZINIT_SOCKET_PATH") { |  | ||||||
|         println!("🔍 ZINIT_SOCKET_PATH environment variable set to: {}", socket_path); |  | ||||||
|     } else { |  | ||||||
|         println!("🔍 ZINIT_SOCKET_PATH environment variable not set"); |  | ||||||
|     } |  | ||||||
|     println!(); |  | ||||||
|      |  | ||||||
|     println!("🚀 Creating service manager..."); |  | ||||||
|     match create_service_manager() { |  | ||||||
|         Ok(_manager) => { |  | ||||||
|             println!("✅ Service manager created successfully!"); |  | ||||||
|              |  | ||||||
|             #[cfg(target_os = "macos")] |  | ||||||
|             println!("📱 Platform: macOS - Using launchctl"); |  | ||||||
|              |  | ||||||
|             #[cfg(target_os = "linux")] |  | ||||||
|             println!("🐧 Platform: Linux - Check logs above for socket discovery details"); |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             println!("❌ Failed to create service manager: {}", e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     println!(); |  | ||||||
|     println!("=== Test Complete ==="); |  | ||||||
|     println!(); |  | ||||||
|     println!("To test zinit socket discovery on Linux:"); |  | ||||||
|     println!("1. Start zinit: zinit -s /tmp/zinit.sock init"); |  | ||||||
|     println!("2. Run with logging: RUST_LOG=debug cargo run --example socket_discovery_test -p sal-service-manager"); |  | ||||||
|     println!("3. Or set custom path: ZINIT_SOCKET_PATH=/custom/path.sock RUST_LOG=debug cargo run --example socket_discovery_test -p sal-service-manager"); |  | ||||||
| } |  | ||||||
| @@ -1,492 +0,0 @@ | |||||||
| use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus}; |  | ||||||
| use once_cell::sync::Lazy; |  | ||||||
| use serde::{Deserialize, Serialize}; |  | ||||||
| use std::collections::HashMap; |  | ||||||
| use std::path::PathBuf; |  | ||||||
| use tokio::process::Command; |  | ||||||
| use tokio::runtime::Runtime; |  | ||||||
|  |  | ||||||
| // Shared runtime for async operations - production-safe initialization |  | ||||||
| static ASYNC_RUNTIME: Lazy<Option<Runtime>> = Lazy::new(|| Runtime::new().ok()); |  | ||||||
|  |  | ||||||
| /// Get the async runtime, creating a temporary one if the static runtime failed |  | ||||||
| fn get_runtime() -> Result<Runtime, ServiceManagerError> { |  | ||||||
|     // Try to use the static runtime first |  | ||||||
|     if let Some(_runtime) = ASYNC_RUNTIME.as_ref() { |  | ||||||
|         // We can't return a reference to the static runtime because we need ownership |  | ||||||
|         // for block_on, so we create a new one. This is a reasonable trade-off for safety. |  | ||||||
|         Runtime::new().map_err(|e| { |  | ||||||
|             ServiceManagerError::Other(format!("Failed to create async runtime: {}", e)) |  | ||||||
|         }) |  | ||||||
|     } else { |  | ||||||
|         // Static runtime failed, try to create a new one |  | ||||||
|         Runtime::new().map_err(|e| { |  | ||||||
|             ServiceManagerError::Other(format!("Failed to create async runtime: {}", e)) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub struct LaunchctlServiceManager { |  | ||||||
|     service_prefix: String, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Serialize, Deserialize)] |  | ||||||
| struct LaunchDaemon { |  | ||||||
|     #[serde(rename = "Label")] |  | ||||||
|     label: String, |  | ||||||
|     #[serde(rename = "ProgramArguments")] |  | ||||||
|     program_arguments: Vec<String>, |  | ||||||
|     #[serde(rename = "WorkingDirectory", skip_serializing_if = "Option::is_none")] |  | ||||||
|     working_directory: Option<String>, |  | ||||||
|     #[serde( |  | ||||||
|         rename = "EnvironmentVariables", |  | ||||||
|         skip_serializing_if = "Option::is_none" |  | ||||||
|     )] |  | ||||||
|     environment_variables: Option<HashMap<String, String>>, |  | ||||||
|     #[serde(rename = "KeepAlive", skip_serializing_if = "Option::is_none")] |  | ||||||
|     keep_alive: Option<bool>, |  | ||||||
|     #[serde(rename = "RunAtLoad")] |  | ||||||
|     run_at_load: bool, |  | ||||||
|     #[serde(rename = "StandardOutPath", skip_serializing_if = "Option::is_none")] |  | ||||||
|     standard_out_path: Option<String>, |  | ||||||
|     #[serde(rename = "StandardErrorPath", skip_serializing_if = "Option::is_none")] |  | ||||||
|     standard_error_path: Option<String>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl LaunchctlServiceManager { |  | ||||||
|     pub fn new() -> Self { |  | ||||||
|         Self { |  | ||||||
|             service_prefix: "tf.ourworld.circles".to_string(), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn get_service_label(&self, service_name: &str) -> String { |  | ||||||
|         format!("{}.{}", self.service_prefix, service_name) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn get_plist_path(&self, service_name: &str) -> PathBuf { |  | ||||||
|         let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); |  | ||||||
|         PathBuf::from(home) |  | ||||||
|             .join("Library") |  | ||||||
|             .join("LaunchAgents") |  | ||||||
|             .join(format!("{}.plist", self.get_service_label(service_name))) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn get_log_path(&self, service_name: &str) -> PathBuf { |  | ||||||
|         let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); |  | ||||||
|         PathBuf::from(home) |  | ||||||
|             .join("Library") |  | ||||||
|             .join("Logs") |  | ||||||
|             .join("circles") |  | ||||||
|             .join(format!("{}.log", service_name)) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn create_plist(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { |  | ||||||
|         let label = self.get_service_label(&config.name); |  | ||||||
|         let plist_path = self.get_plist_path(&config.name); |  | ||||||
|         let log_path = self.get_log_path(&config.name); |  | ||||||
|  |  | ||||||
|         // Ensure the LaunchAgents directory exists |  | ||||||
|         if let Some(parent) = plist_path.parent() { |  | ||||||
|             tokio::fs::create_dir_all(parent).await?; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Ensure the logs directory exists |  | ||||||
|         if let Some(parent) = log_path.parent() { |  | ||||||
|             tokio::fs::create_dir_all(parent).await?; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         let mut program_arguments = vec![config.binary_path.clone()]; |  | ||||||
|         program_arguments.extend(config.args.clone()); |  | ||||||
|  |  | ||||||
|         let launch_daemon = LaunchDaemon { |  | ||||||
|             label: label.clone(), |  | ||||||
|             program_arguments, |  | ||||||
|             working_directory: config.working_directory.clone(), |  | ||||||
|             environment_variables: if config.environment.is_empty() { |  | ||||||
|                 None |  | ||||||
|             } else { |  | ||||||
|                 Some(config.environment.clone()) |  | ||||||
|             }, |  | ||||||
|             keep_alive: if config.auto_restart { |  | ||||||
|                 Some(true) |  | ||||||
|             } else { |  | ||||||
|                 None |  | ||||||
|             }, |  | ||||||
|             run_at_load: true, |  | ||||||
|             standard_out_path: Some(log_path.to_string_lossy().to_string()), |  | ||||||
|             standard_error_path: Some(log_path.to_string_lossy().to_string()), |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         let mut plist_content = Vec::new(); |  | ||||||
|         plist::to_writer_xml(&mut plist_content, &launch_daemon) |  | ||||||
|             .map_err(|e| ServiceManagerError::Other(format!("Failed to serialize plist: {}", e)))?; |  | ||||||
|         let plist_content = String::from_utf8(plist_content).map_err(|e| { |  | ||||||
|             ServiceManagerError::Other(format!("Failed to convert plist to string: {}", e)) |  | ||||||
|         })?; |  | ||||||
|  |  | ||||||
|         tokio::fs::write(&plist_path, plist_content).await?; |  | ||||||
|  |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn run_launchctl(&self, args: &[&str]) -> Result<String, ServiceManagerError> { |  | ||||||
|         let output = Command::new("launchctl").args(args).output().await?; |  | ||||||
|  |  | ||||||
|         if !output.status.success() { |  | ||||||
|             let stderr = String::from_utf8_lossy(&output.stderr); |  | ||||||
|             return Err(ServiceManagerError::Other(format!( |  | ||||||
|                 "launchctl command failed: {}", |  | ||||||
|                 stderr |  | ||||||
|             ))); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Ok(String::from_utf8_lossy(&output.stdout).to_string()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn wait_for_service_status( |  | ||||||
|         &self, |  | ||||||
|         service_name: &str, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<(), ServiceManagerError> { |  | ||||||
|         use tokio::time::{sleep, timeout, Duration}; |  | ||||||
|  |  | ||||||
|         let timeout_duration = Duration::from_secs(timeout_secs); |  | ||||||
|         let poll_interval = Duration::from_millis(500); |  | ||||||
|  |  | ||||||
|         let result = timeout(timeout_duration, async { |  | ||||||
|             loop { |  | ||||||
|                 match self.status(service_name) { |  | ||||||
|                     Ok(ServiceStatus::Running) => { |  | ||||||
|                         return Ok(()); |  | ||||||
|                     } |  | ||||||
|                     Ok(ServiceStatus::Failed) => { |  | ||||||
|                         // Service failed, get error details from logs |  | ||||||
|                         let logs = self.logs(service_name, Some(20)).unwrap_or_default(); |  | ||||||
|                         let error_msg = if logs.is_empty() { |  | ||||||
|                             "Service failed to start (no logs available)".to_string() |  | ||||||
|                         } else { |  | ||||||
|                             // Extract error lines from logs |  | ||||||
|                             let error_lines: Vec<&str> = logs |  | ||||||
|                                 .lines() |  | ||||||
|                                 .filter(|line| { |  | ||||||
|                                     line.to_lowercase().contains("error") |  | ||||||
|                                         || line.to_lowercase().contains("failed") |  | ||||||
|                                 }) |  | ||||||
|                                 .take(3) |  | ||||||
|                                 .collect(); |  | ||||||
|  |  | ||||||
|                             if error_lines.is_empty() { |  | ||||||
|                                 format!( |  | ||||||
|                                     "Service failed to start. Recent logs:\n{}", |  | ||||||
|                                     logs.lines() |  | ||||||
|                                         .rev() |  | ||||||
|                                         .take(5) |  | ||||||
|                                         .collect::<Vec<_>>() |  | ||||||
|                                         .into_iter() |  | ||||||
|                                         .rev() |  | ||||||
|                                         .collect::<Vec<_>>() |  | ||||||
|                                         .join("\n") |  | ||||||
|                                 ) |  | ||||||
|                             } else { |  | ||||||
|                                 format!( |  | ||||||
|                                     "Service failed to start. Errors:\n{}", |  | ||||||
|                                     error_lines.join("\n") |  | ||||||
|                                 ) |  | ||||||
|                             } |  | ||||||
|                         }; |  | ||||||
|                         return Err(ServiceManagerError::StartFailed( |  | ||||||
|                             service_name.to_string(), |  | ||||||
|                             error_msg, |  | ||||||
|                         )); |  | ||||||
|                     } |  | ||||||
|                     Ok(ServiceStatus::Stopped) | Ok(ServiceStatus::Unknown) => { |  | ||||||
|                         // Still starting, continue polling |  | ||||||
|                         sleep(poll_interval).await; |  | ||||||
|                     } |  | ||||||
|                     Err(ServiceManagerError::ServiceNotFound(_)) => { |  | ||||||
|                         return Err(ServiceManagerError::ServiceNotFound( |  | ||||||
|                             service_name.to_string(), |  | ||||||
|                         )); |  | ||||||
|                     } |  | ||||||
|                     Err(e) => { |  | ||||||
|                         return Err(e); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|         .await; |  | ||||||
|  |  | ||||||
|         match result { |  | ||||||
|             Ok(Ok(())) => Ok(()), |  | ||||||
|             Ok(Err(e)) => Err(e), |  | ||||||
|             Err(_) => Err(ServiceManagerError::StartFailed( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|                 format!("Service did not start within {} seconds", timeout_secs), |  | ||||||
|             )), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl ServiceManager for LaunchctlServiceManager { |  | ||||||
|     fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError> { |  | ||||||
|         let plist_path = self.get_plist_path(service_name); |  | ||||||
|         Ok(plist_path.exists()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { |  | ||||||
|         // Use production-safe runtime for async operations |  | ||||||
|         let runtime = get_runtime()?; |  | ||||||
|         runtime.block_on(async { |  | ||||||
|             let label = self.get_service_label(&config.name); |  | ||||||
|  |  | ||||||
|             // Check if service is already loaded |  | ||||||
|             let list_output = self.run_launchctl(&["list"]).await?; |  | ||||||
|             if list_output.contains(&label) { |  | ||||||
|                 return Err(ServiceManagerError::ServiceAlreadyExists( |  | ||||||
|                     config.name.clone(), |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Create the plist file |  | ||||||
|             self.create_plist(config).await?; |  | ||||||
|  |  | ||||||
|             // Load the service |  | ||||||
|             let plist_path = self.get_plist_path(&config.name); |  | ||||||
|             self.run_launchctl(&["load", &plist_path.to_string_lossy()]) |  | ||||||
|                 .await |  | ||||||
|                 .map_err(|e| { |  | ||||||
|                     ServiceManagerError::StartFailed(config.name.clone(), e.to_string()) |  | ||||||
|                 })?; |  | ||||||
|  |  | ||||||
|             Ok(()) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         let runtime = get_runtime()?; |  | ||||||
|         runtime.block_on(async { |  | ||||||
|             let label = self.get_service_label(service_name); |  | ||||||
|             let plist_path = self.get_plist_path(service_name); |  | ||||||
|  |  | ||||||
|             // Check if plist file exists |  | ||||||
|             if !plist_path.exists() { |  | ||||||
|                 return Err(ServiceManagerError::ServiceNotFound( |  | ||||||
|                     service_name.to_string(), |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Check if service is already loaded and running |  | ||||||
|             let list_output = self.run_launchctl(&["list"]).await?; |  | ||||||
|             if list_output.contains(&label) { |  | ||||||
|                 // Service is loaded, check if it's running |  | ||||||
|                 match self.status(service_name)? { |  | ||||||
|                     ServiceStatus::Running => { |  | ||||||
|                         return Ok(()); // Already running, nothing to do |  | ||||||
|                     } |  | ||||||
|                     _ => { |  | ||||||
|                         // Service is loaded but not running, try to start it |  | ||||||
|                         self.run_launchctl(&["start", &label]).await.map_err(|e| { |  | ||||||
|                             ServiceManagerError::StartFailed( |  | ||||||
|                                 service_name.to_string(), |  | ||||||
|                                 e.to_string(), |  | ||||||
|                             ) |  | ||||||
|                         })?; |  | ||||||
|                         return Ok(()); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Service is not loaded, load it |  | ||||||
|             self.run_launchctl(&["load", &plist_path.to_string_lossy()]) |  | ||||||
|                 .await |  | ||||||
|                 .map_err(|e| { |  | ||||||
|                     ServiceManagerError::StartFailed(service_name.to_string(), e.to_string()) |  | ||||||
|                 })?; |  | ||||||
|  |  | ||||||
|             Ok(()) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start_and_confirm( |  | ||||||
|         &self, |  | ||||||
|         config: &ServiceConfig, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<(), ServiceManagerError> { |  | ||||||
|         // First start the service |  | ||||||
|         self.start(config)?; |  | ||||||
|  |  | ||||||
|         // Then wait for confirmation using production-safe runtime |  | ||||||
|         let runtime = get_runtime()?; |  | ||||||
|         runtime.block_on(async { |  | ||||||
|             self.wait_for_service_status(&config.name, timeout_secs) |  | ||||||
|                 .await |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start_existing_and_confirm( |  | ||||||
|         &self, |  | ||||||
|         service_name: &str, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<(), ServiceManagerError> { |  | ||||||
|         // First start the existing service |  | ||||||
|         self.start_existing(service_name)?; |  | ||||||
|  |  | ||||||
|         // Then wait for confirmation using production-safe runtime |  | ||||||
|         let runtime = get_runtime()?; |  | ||||||
|         runtime.block_on(async { |  | ||||||
|             self.wait_for_service_status(service_name, timeout_secs) |  | ||||||
|                 .await |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         let runtime = get_runtime()?; |  | ||||||
|         runtime.block_on(async { |  | ||||||
|             let _label = self.get_service_label(service_name); |  | ||||||
|             let plist_path = self.get_plist_path(service_name); |  | ||||||
|  |  | ||||||
|             // Unload the service |  | ||||||
|             self.run_launchctl(&["unload", &plist_path.to_string_lossy()]) |  | ||||||
|                 .await |  | ||||||
|                 .map_err(|e| { |  | ||||||
|                     ServiceManagerError::StopFailed(service_name.to_string(), e.to_string()) |  | ||||||
|                 })?; |  | ||||||
|  |  | ||||||
|             Ok(()) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         // For launchctl, we stop and start |  | ||||||
|         if let Err(e) = self.stop(service_name) { |  | ||||||
|             // If stop fails because service doesn't exist, that's ok for restart |  | ||||||
|             if !matches!(e, ServiceManagerError::ServiceNotFound(_)) { |  | ||||||
|                 return Err(ServiceManagerError::RestartFailed( |  | ||||||
|                     service_name.to_string(), |  | ||||||
|                     e.to_string(), |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // We need the config to restart, but we don't have it stored |  | ||||||
|         // For now, return an error - in a real implementation we might store configs |  | ||||||
|         Err(ServiceManagerError::RestartFailed( |  | ||||||
|             service_name.to_string(), |  | ||||||
|             "Restart requires re-providing service configuration".to_string(), |  | ||||||
|         )) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> { |  | ||||||
|         let runtime = get_runtime()?; |  | ||||||
|         runtime.block_on(async { |  | ||||||
|             let label = self.get_service_label(service_name); |  | ||||||
|             let plist_path = self.get_plist_path(service_name); |  | ||||||
|  |  | ||||||
|             // First check if the plist file exists |  | ||||||
|             if !plist_path.exists() { |  | ||||||
|                 return Err(ServiceManagerError::ServiceNotFound( |  | ||||||
|                     service_name.to_string(), |  | ||||||
|                 )); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             let list_output = self.run_launchctl(&["list"]).await?; |  | ||||||
|  |  | ||||||
|             if !list_output.contains(&label) { |  | ||||||
|                 return Ok(ServiceStatus::Stopped); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             // Get detailed status |  | ||||||
|             match self.run_launchctl(&["list", &label]).await { |  | ||||||
|                 Ok(output) => { |  | ||||||
|                     if output.contains("\"PID\" = ") { |  | ||||||
|                         Ok(ServiceStatus::Running) |  | ||||||
|                     } else if output.contains("\"LastExitStatus\" = ") { |  | ||||||
|                         Ok(ServiceStatus::Failed) |  | ||||||
|                     } else { |  | ||||||
|                         Ok(ServiceStatus::Unknown) |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 Err(_) => Ok(ServiceStatus::Stopped), |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn logs( |  | ||||||
|         &self, |  | ||||||
|         service_name: &str, |  | ||||||
|         lines: Option<usize>, |  | ||||||
|     ) -> Result<String, ServiceManagerError> { |  | ||||||
|         let runtime = get_runtime()?; |  | ||||||
|         runtime.block_on(async { |  | ||||||
|             let log_path = self.get_log_path(service_name); |  | ||||||
|  |  | ||||||
|             if !log_path.exists() { |  | ||||||
|                 return Ok(String::new()); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             match lines { |  | ||||||
|                 Some(n) => { |  | ||||||
|                     let output = Command::new("tail") |  | ||||||
|                         .args(&["-n", &n.to_string(), &log_path.to_string_lossy()]) |  | ||||||
|                         .output() |  | ||||||
|                         .await?; |  | ||||||
|                     Ok(String::from_utf8_lossy(&output.stdout).to_string()) |  | ||||||
|                 } |  | ||||||
|                 None => { |  | ||||||
|                     let content = tokio::fs::read_to_string(&log_path).await?; |  | ||||||
|                     Ok(content) |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn list(&self) -> Result<Vec<String>, ServiceManagerError> { |  | ||||||
|         let runtime = get_runtime()?; |  | ||||||
|         runtime.block_on(async { |  | ||||||
|             let list_output = self.run_launchctl(&["list"]).await?; |  | ||||||
|  |  | ||||||
|             let services: Vec<String> = list_output |  | ||||||
|                 .lines() |  | ||||||
|                 .filter_map(|line| { |  | ||||||
|                     if line.contains(&self.service_prefix) { |  | ||||||
|                         // Extract service name from label |  | ||||||
|                         line.split_whitespace() |  | ||||||
|                             .last() |  | ||||||
|                             .and_then(|label| { |  | ||||||
|                                 label.strip_prefix(&format!("{}.", self.service_prefix)) |  | ||||||
|                             }) |  | ||||||
|                             .map(|s| s.to_string()) |  | ||||||
|                     } else { |  | ||||||
|                         None |  | ||||||
|                     } |  | ||||||
|                 }) |  | ||||||
|                 .collect(); |  | ||||||
|  |  | ||||||
|             Ok(services) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         // Try to stop the service first, but don't fail if it's already stopped or doesn't exist |  | ||||||
|         if let Err(e) = self.stop(service_name) { |  | ||||||
|             // Log the error but continue with removal |  | ||||||
|             log::warn!( |  | ||||||
|                 "Failed to stop service '{}' before removal: {}", |  | ||||||
|                 service_name, |  | ||||||
|                 e |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Remove the plist file using production-safe runtime |  | ||||||
|         let runtime = get_runtime()?; |  | ||||||
|         runtime.block_on(async { |  | ||||||
|             let plist_path = self.get_plist_path(service_name); |  | ||||||
|             if plist_path.exists() { |  | ||||||
|                 tokio::fs::remove_file(&plist_path).await?; |  | ||||||
|             } |  | ||||||
|             Ok(()) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,301 +0,0 @@ | |||||||
| use std::collections::HashMap; |  | ||||||
| use thiserror::Error; |  | ||||||
|  |  | ||||||
| #[derive(Error, Debug)] |  | ||||||
| pub enum ServiceManagerError { |  | ||||||
|     #[error("Service '{0}' not found")] |  | ||||||
|     ServiceNotFound(String), |  | ||||||
|     #[error("Service '{0}' already exists")] |  | ||||||
|     ServiceAlreadyExists(String), |  | ||||||
|     #[error("Failed to start service '{0}': {1}")] |  | ||||||
|     StartFailed(String, String), |  | ||||||
|     #[error("Failed to stop service '{0}': {1}")] |  | ||||||
|     StopFailed(String, String), |  | ||||||
|     #[error("Failed to restart service '{0}': {1}")] |  | ||||||
|     RestartFailed(String, String), |  | ||||||
|     #[error("Failed to get logs for service '{0}': {1}")] |  | ||||||
|     LogsFailed(String, String), |  | ||||||
|     #[error("IO error: {0}")] |  | ||||||
|     IoError(#[from] std::io::Error), |  | ||||||
|     #[error("Service manager error: {0}")] |  | ||||||
|     Other(String), |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub struct ServiceConfig { |  | ||||||
|     pub name: String, |  | ||||||
|     pub binary_path: String, |  | ||||||
|     pub args: Vec<String>, |  | ||||||
|     pub working_directory: Option<String>, |  | ||||||
|     pub environment: HashMap<String, String>, |  | ||||||
|     pub auto_restart: bool, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, PartialEq)] |  | ||||||
| pub enum ServiceStatus { |  | ||||||
|     Running, |  | ||||||
|     Stopped, |  | ||||||
|     Failed, |  | ||||||
|     Unknown, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub trait ServiceManager: Send + Sync { |  | ||||||
|     /// Check if a service exists |  | ||||||
|     fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// Start a service with the given configuration |  | ||||||
|     fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// Start an existing service by name (load existing plist/config) |  | ||||||
|     fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// Start a service and wait for confirmation that it's running or failed |  | ||||||
|     fn start_and_confirm( |  | ||||||
|         &self, |  | ||||||
|         config: &ServiceConfig, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<(), ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// Start an existing service and wait for confirmation that it's running or failed |  | ||||||
|     fn start_existing_and_confirm( |  | ||||||
|         &self, |  | ||||||
|         service_name: &str, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<(), ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// Stop a service by name |  | ||||||
|     fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// Restart a service by name |  | ||||||
|     fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// Get the status of a service |  | ||||||
|     fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// Get logs for a service |  | ||||||
|     fn logs(&self, service_name: &str, lines: Option<usize>) |  | ||||||
|         -> Result<String, ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// List all managed services |  | ||||||
|     fn list(&self) -> Result<Vec<String>, ServiceManagerError>; |  | ||||||
|  |  | ||||||
|     /// Remove a service configuration (stop if running) |  | ||||||
|     fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError>; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Platform-specific implementations |  | ||||||
| #[cfg(target_os = "macos")] |  | ||||||
| mod launchctl; |  | ||||||
| #[cfg(target_os = "macos")] |  | ||||||
| pub use launchctl::LaunchctlServiceManager; |  | ||||||
|  |  | ||||||
| #[cfg(target_os = "linux")] |  | ||||||
| mod systemd; |  | ||||||
| #[cfg(target_os = "linux")] |  | ||||||
| pub use systemd::SystemdServiceManager; |  | ||||||
|  |  | ||||||
| mod zinit; |  | ||||||
| pub use zinit::ZinitServiceManager; |  | ||||||
|  |  | ||||||
| #[cfg(feature = "rhai")] |  | ||||||
| pub mod rhai; |  | ||||||
|  |  | ||||||
| /// Discover available zinit socket paths |  | ||||||
| /// |  | ||||||
| /// This function checks for zinit sockets in the following order: |  | ||||||
| /// 1. Environment variable ZINIT_SOCKET_PATH (if set) |  | ||||||
| /// 2. Common socket locations with connectivity testing |  | ||||||
| /// |  | ||||||
| /// # Returns |  | ||||||
| /// |  | ||||||
| /// Returns the first working socket path found, or None if no working zinit server is detected. |  | ||||||
| #[cfg(target_os = "linux")] |  | ||||||
| fn discover_zinit_socket() -> Option<String> { |  | ||||||
|     // First check environment variable |  | ||||||
|     if let Ok(env_socket_path) = std::env::var("ZINIT_SOCKET_PATH") { |  | ||||||
|         log::debug!("Checking ZINIT_SOCKET_PATH: {}", env_socket_path); |  | ||||||
|         if test_zinit_socket(&env_socket_path) { |  | ||||||
|             log::info!( |  | ||||||
|                 "Using zinit socket from ZINIT_SOCKET_PATH: {}", |  | ||||||
|                 env_socket_path |  | ||||||
|             ); |  | ||||||
|             return Some(env_socket_path); |  | ||||||
|         } else { |  | ||||||
|             log::warn!( |  | ||||||
|                 "ZINIT_SOCKET_PATH specified but socket is not accessible: {}", |  | ||||||
|                 env_socket_path |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Try common socket locations |  | ||||||
|     let common_paths = [ |  | ||||||
|         "/var/run/zinit.sock", |  | ||||||
|         "/tmp/zinit.sock", |  | ||||||
|         "/run/zinit.sock", |  | ||||||
|         "./zinit.sock", |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     log::debug!("Discovering zinit socket from common locations..."); |  | ||||||
|     for path in &common_paths { |  | ||||||
|         log::debug!("Testing socket path: {}", path); |  | ||||||
|         if test_zinit_socket(path) { |  | ||||||
|             log::info!("Found working zinit socket at: {}", path); |  | ||||||
|             return Some(path.to_string()); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     log::debug!("No working zinit socket found"); |  | ||||||
|     None |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Test if a zinit socket is accessible and responsive |  | ||||||
| /// |  | ||||||
| /// This function attempts to create a ZinitServiceManager and perform a basic |  | ||||||
| /// connectivity test by listing services. |  | ||||||
| #[cfg(target_os = "linux")] |  | ||||||
| fn test_zinit_socket(socket_path: &str) -> bool { |  | ||||||
|     // Check if socket file exists first |  | ||||||
|     if !std::path::Path::new(socket_path).exists() { |  | ||||||
|         log::debug!("Socket file does not exist: {}", socket_path); |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Try to create a manager and test basic connectivity |  | ||||||
|     match ZinitServiceManager::new(socket_path) { |  | ||||||
|         Ok(manager) => { |  | ||||||
|             // Test basic connectivity by trying to list services |  | ||||||
|             match manager.list() { |  | ||||||
|                 Ok(_) => { |  | ||||||
|                     log::debug!("Socket {} is responsive", socket_path); |  | ||||||
|                     true |  | ||||||
|                 } |  | ||||||
|                 Err(e) => { |  | ||||||
|                     log::debug!("Socket {} exists but not responsive: {}", socket_path, e); |  | ||||||
|                     false |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             log::debug!("Failed to create manager for socket {}: {}", socket_path, e); |  | ||||||
|             false |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Create a service manager appropriate for the current platform |  | ||||||
| /// |  | ||||||
| /// - On macOS: Uses launchctl for service management |  | ||||||
| /// - On Linux: Uses zinit for service management with systemd fallback |  | ||||||
| /// |  | ||||||
| /// # Returns |  | ||||||
| /// |  | ||||||
| /// Returns a Result containing the service manager or an error if initialization fails. |  | ||||||
| /// On Linux, it first tries to discover a working zinit socket. If no zinit server is found, |  | ||||||
| /// it will fall back to systemd. |  | ||||||
| /// |  | ||||||
| /// # Environment Variables |  | ||||||
| /// |  | ||||||
| /// - `ZINIT_SOCKET_PATH`: Specifies the zinit socket path (Linux only) |  | ||||||
| /// |  | ||||||
| /// # Errors |  | ||||||
| /// |  | ||||||
| /// Returns `ServiceManagerError` if: |  | ||||||
| /// - The platform is not supported (Windows, etc.) |  | ||||||
| /// - Service manager initialization fails on all available backends |  | ||||||
| pub fn create_service_manager() -> Result<Box<dyn ServiceManager>, ServiceManagerError> { |  | ||||||
|     #[cfg(target_os = "macos")] |  | ||||||
|     { |  | ||||||
|         Ok(Box::new(LaunchctlServiceManager::new())) |  | ||||||
|     } |  | ||||||
|     #[cfg(target_os = "linux")] |  | ||||||
|     { |  | ||||||
|         // Try to discover a working zinit socket |  | ||||||
|         if let Some(socket_path) = discover_zinit_socket() { |  | ||||||
|             match ZinitServiceManager::new(&socket_path) { |  | ||||||
|                 Ok(zinit_manager) => { |  | ||||||
|                     log::info!("Using zinit service manager with socket: {}", socket_path); |  | ||||||
|                     return Ok(Box::new(zinit_manager)); |  | ||||||
|                 } |  | ||||||
|                 Err(zinit_error) => { |  | ||||||
|                     log::warn!( |  | ||||||
|                         "Failed to create zinit manager for discovered socket {}: {}", |  | ||||||
|                         socket_path, |  | ||||||
|                         zinit_error |  | ||||||
|                     ); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             log::info!("No running zinit server detected. To use zinit, start it with: zinit -s /tmp/zinit.sock init"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Fallback to systemd |  | ||||||
|         log::info!("Falling back to systemd service manager"); |  | ||||||
|         Ok(Box::new(SystemdServiceManager::new())) |  | ||||||
|     } |  | ||||||
|     #[cfg(not(any(target_os = "macos", target_os = "linux")))] |  | ||||||
|     { |  | ||||||
|         Err(ServiceManagerError::Other( |  | ||||||
|             "Service manager not implemented for this platform".to_string(), |  | ||||||
|         )) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Create a service manager for zinit with a custom socket path |  | ||||||
| /// |  | ||||||
| /// This is useful when zinit is running with a non-default socket path |  | ||||||
| pub fn create_zinit_service_manager( |  | ||||||
|     socket_path: &str, |  | ||||||
| ) -> Result<Box<dyn ServiceManager>, ServiceManagerError> { |  | ||||||
|     Ok(Box::new(ZinitServiceManager::new(socket_path)?)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Create a service manager for systemd (Linux alternative) |  | ||||||
| /// |  | ||||||
| /// This creates a systemd-based service manager as an alternative to zinit on Linux |  | ||||||
| #[cfg(target_os = "linux")] |  | ||||||
| pub fn create_systemd_service_manager() -> Box<dyn ServiceManager> { |  | ||||||
|     Box::new(SystemdServiceManager::new()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod tests { |  | ||||||
|     use super::*; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_create_service_manager() { |  | ||||||
|         // This test ensures the service manager can be created without panicking |  | ||||||
|         let result = create_service_manager(); |  | ||||||
|         assert!(result.is_ok(), "Service manager creation should succeed"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[cfg(target_os = "linux")] |  | ||||||
|     #[test] |  | ||||||
|     fn test_socket_discovery_with_env_var() { |  | ||||||
|         // Test that environment variable is respected |  | ||||||
|         std::env::set_var("ZINIT_SOCKET_PATH", "/test/path.sock"); |  | ||||||
|  |  | ||||||
|         // The discover function should check the env var first |  | ||||||
|         // Since the socket doesn't exist, it should return None, but we can't test |  | ||||||
|         // the actual discovery logic without a real socket |  | ||||||
|  |  | ||||||
|         std::env::remove_var("ZINIT_SOCKET_PATH"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[cfg(target_os = "linux")] |  | ||||||
|     #[test] |  | ||||||
|     fn test_socket_discovery_without_env_var() { |  | ||||||
|         // Ensure env var is not set |  | ||||||
|         std::env::remove_var("ZINIT_SOCKET_PATH"); |  | ||||||
|  |  | ||||||
|         // The discover function should try common paths |  | ||||||
|         // Since no zinit is running, it should return None |  | ||||||
|         let result = discover_zinit_socket(); |  | ||||||
|  |  | ||||||
|         // This is expected to be None in test environment |  | ||||||
|         assert!( |  | ||||||
|             result.is_none(), |  | ||||||
|             "Should return None when no zinit server is running" |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,256 +0,0 @@ | |||||||
| //! Rhai integration for the service manager module |  | ||||||
| //! |  | ||||||
| //! This module provides Rhai scripting support for service management operations. |  | ||||||
|  |  | ||||||
| use crate::{create_service_manager, ServiceConfig, ServiceManager}; |  | ||||||
| use rhai::{Engine, EvalAltResult, Map}; |  | ||||||
| use std::collections::HashMap; |  | ||||||
| use std::sync::Arc; |  | ||||||
|  |  | ||||||
| /// A wrapper around ServiceManager that can be used in Rhai |  | ||||||
| #[derive(Clone)] |  | ||||||
| pub struct RhaiServiceManager { |  | ||||||
|     inner: Arc<Box<dyn ServiceManager>>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl RhaiServiceManager { |  | ||||||
|     pub fn new() -> Result<Self, Box<EvalAltResult>> { |  | ||||||
|         let manager = create_service_manager() |  | ||||||
|             .map_err(|e| format!("Failed to create service manager: {}", e))?; |  | ||||||
|         Ok(Self { |  | ||||||
|             inner: Arc::new(manager), |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Register the service manager module with a Rhai engine |  | ||||||
| pub fn register_service_manager_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> { |  | ||||||
|     // Factory function to create service manager |  | ||||||
|     engine.register_type::<RhaiServiceManager>(); |  | ||||||
|     engine.register_fn( |  | ||||||
|         "create_service_manager", |  | ||||||
|         || -> Result<RhaiServiceManager, Box<EvalAltResult>> { RhaiServiceManager::new() }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     // Service management functions |  | ||||||
|     engine.register_fn( |  | ||||||
|         "start", |  | ||||||
|         |manager: &mut RhaiServiceManager, config: Map| -> Result<(), Box<EvalAltResult>> { |  | ||||||
|             let service_config = map_to_service_config(config)?; |  | ||||||
|             manager |  | ||||||
|                 .inner |  | ||||||
|                 .start(&service_config) |  | ||||||
|                 .map_err(|e| format!("Failed to start service: {}", e).into()) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     engine.register_fn( |  | ||||||
|         "stop", |  | ||||||
|         |manager: &mut RhaiServiceManager, |  | ||||||
|          service_name: String| |  | ||||||
|          -> Result<(), Box<EvalAltResult>> { |  | ||||||
|             manager |  | ||||||
|                 .inner |  | ||||||
|                 .stop(&service_name) |  | ||||||
|                 .map_err(|e| format!("Failed to stop service: {}", e).into()) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     engine.register_fn( |  | ||||||
|         "restart", |  | ||||||
|         |manager: &mut RhaiServiceManager, |  | ||||||
|          service_name: String| |  | ||||||
|          -> Result<(), Box<EvalAltResult>> { |  | ||||||
|             manager |  | ||||||
|                 .inner |  | ||||||
|                 .restart(&service_name) |  | ||||||
|                 .map_err(|e| format!("Failed to restart service: {}", e).into()) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     engine.register_fn( |  | ||||||
|         "status", |  | ||||||
|         |manager: &mut RhaiServiceManager, |  | ||||||
|          service_name: String| |  | ||||||
|          -> Result<String, Box<EvalAltResult>> { |  | ||||||
|             let status = manager |  | ||||||
|                 .inner |  | ||||||
|                 .status(&service_name) |  | ||||||
|                 .map_err(|e| format!("Failed to get service status: {}", e))?; |  | ||||||
|             Ok(format!("{:?}", status)) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     engine.register_fn( |  | ||||||
|         "logs", |  | ||||||
|         |manager: &mut RhaiServiceManager, |  | ||||||
|          service_name: String, |  | ||||||
|          lines: i64| |  | ||||||
|          -> Result<String, Box<EvalAltResult>> { |  | ||||||
|             let lines_opt = if lines > 0 { |  | ||||||
|                 Some(lines as usize) |  | ||||||
|             } else { |  | ||||||
|                 None |  | ||||||
|             }; |  | ||||||
|             manager |  | ||||||
|                 .inner |  | ||||||
|                 .logs(&service_name, lines_opt) |  | ||||||
|                 .map_err(|e| format!("Failed to get service logs: {}", e).into()) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     engine.register_fn( |  | ||||||
|         "list", |  | ||||||
|         |manager: &mut RhaiServiceManager| -> Result<Vec<String>, Box<EvalAltResult>> { |  | ||||||
|             manager |  | ||||||
|                 .inner |  | ||||||
|                 .list() |  | ||||||
|                 .map_err(|e| format!("Failed to list services: {}", e).into()) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     engine.register_fn( |  | ||||||
|         "remove", |  | ||||||
|         |manager: &mut RhaiServiceManager, |  | ||||||
|          service_name: String| |  | ||||||
|          -> Result<(), Box<EvalAltResult>> { |  | ||||||
|             manager |  | ||||||
|                 .inner |  | ||||||
|                 .remove(&service_name) |  | ||||||
|                 .map_err(|e| format!("Failed to remove service: {}", e).into()) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     engine.register_fn( |  | ||||||
|         "exists", |  | ||||||
|         |manager: &mut RhaiServiceManager, |  | ||||||
|          service_name: String| |  | ||||||
|          -> Result<bool, Box<EvalAltResult>> { |  | ||||||
|             manager |  | ||||||
|                 .inner |  | ||||||
|                 .exists(&service_name) |  | ||||||
|                 .map_err(|e| format!("Failed to check if service exists: {}", e).into()) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     engine.register_fn( |  | ||||||
|         "start_and_confirm", |  | ||||||
|         |manager: &mut RhaiServiceManager, |  | ||||||
|          config: Map, |  | ||||||
|          timeout_secs: i64| |  | ||||||
|          -> Result<(), Box<EvalAltResult>> { |  | ||||||
|             let service_config = map_to_service_config(config)?; |  | ||||||
|             let timeout = if timeout_secs > 0 { |  | ||||||
|                 timeout_secs as u64 |  | ||||||
|             } else { |  | ||||||
|                 30 |  | ||||||
|             }; |  | ||||||
|             manager |  | ||||||
|                 .inner |  | ||||||
|                 .start_and_confirm(&service_config, timeout) |  | ||||||
|                 .map_err(|e| format!("Failed to start and confirm service: {}", e).into()) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     engine.register_fn( |  | ||||||
|         "start_existing_and_confirm", |  | ||||||
|         |manager: &mut RhaiServiceManager, |  | ||||||
|          service_name: String, |  | ||||||
|          timeout_secs: i64| |  | ||||||
|          -> Result<(), Box<EvalAltResult>> { |  | ||||||
|             let timeout = if timeout_secs > 0 { |  | ||||||
|                 timeout_secs as u64 |  | ||||||
|             } else { |  | ||||||
|                 30 |  | ||||||
|             }; |  | ||||||
|             manager |  | ||||||
|                 .inner |  | ||||||
|                 .start_existing_and_confirm(&service_name, timeout) |  | ||||||
|                 .map_err(|e| format!("Failed to start existing service and confirm: {}", e).into()) |  | ||||||
|         }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     Ok(()) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Convert a Rhai Map to a ServiceConfig |  | ||||||
| fn map_to_service_config(map: Map) -> Result<ServiceConfig, Box<EvalAltResult>> { |  | ||||||
|     let name = map |  | ||||||
|         .get("name") |  | ||||||
|         .and_then(|v| v.clone().into_string().ok()) |  | ||||||
|         .ok_or("Service config must have a 'name' field")?; |  | ||||||
|  |  | ||||||
|     let binary_path = map |  | ||||||
|         .get("binary_path") |  | ||||||
|         .and_then(|v| v.clone().into_string().ok()) |  | ||||||
|         .ok_or("Service config must have a 'binary_path' field")?; |  | ||||||
|  |  | ||||||
|     let args = map |  | ||||||
|         .get("args") |  | ||||||
|         .and_then(|v| v.clone().try_cast::<rhai::Array>()) |  | ||||||
|         .map(|arr| { |  | ||||||
|             arr.into_iter() |  | ||||||
|                 .filter_map(|v| v.into_string().ok()) |  | ||||||
|                 .collect::<Vec<String>>() |  | ||||||
|         }) |  | ||||||
|         .unwrap_or_default(); |  | ||||||
|  |  | ||||||
|     let working_directory = map |  | ||||||
|         .get("working_directory") |  | ||||||
|         .and_then(|v| v.clone().into_string().ok()); |  | ||||||
|  |  | ||||||
|     let environment = map |  | ||||||
|         .get("environment") |  | ||||||
|         .and_then(|v| v.clone().try_cast::<Map>()) |  | ||||||
|         .map(|env_map| { |  | ||||||
|             env_map |  | ||||||
|                 .into_iter() |  | ||||||
|                 .filter_map(|(k, v)| v.into_string().ok().map(|val| (k.to_string(), val))) |  | ||||||
|                 .collect::<HashMap<String, String>>() |  | ||||||
|         }) |  | ||||||
|         .unwrap_or_default(); |  | ||||||
|  |  | ||||||
|     let auto_restart = map |  | ||||||
|         .get("auto_restart") |  | ||||||
|         .and_then(|v| v.as_bool().ok()) |  | ||||||
|         .unwrap_or(false); |  | ||||||
|  |  | ||||||
|     Ok(ServiceConfig { |  | ||||||
|         name, |  | ||||||
|         binary_path, |  | ||||||
|         args, |  | ||||||
|         working_directory, |  | ||||||
|         environment, |  | ||||||
|         auto_restart, |  | ||||||
|     }) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| mod tests { |  | ||||||
|     use super::*; |  | ||||||
|     use rhai::{Engine, Map}; |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_register_service_manager_module() { |  | ||||||
|         let mut engine = Engine::new(); |  | ||||||
|         register_service_manager_module(&mut engine).unwrap(); |  | ||||||
|  |  | ||||||
|         // Test that the functions are registered |  | ||||||
|         // Note: Rhai doesn't expose a public API to check if functions are registered |  | ||||||
|         // So we'll just verify the module registration doesn't panic |  | ||||||
|         assert!(true); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     #[test] |  | ||||||
|     fn test_map_to_service_config() { |  | ||||||
|         let mut map = Map::new(); |  | ||||||
|         map.insert("name".into(), "test-service".into()); |  | ||||||
|         map.insert("binary_path".into(), "/bin/echo".into()); |  | ||||||
|         map.insert("auto_restart".into(), true.into()); |  | ||||||
|  |  | ||||||
|         let config = map_to_service_config(map).unwrap(); |  | ||||||
|         assert_eq!(config.name, "test-service"); |  | ||||||
|         assert_eq!(config.binary_path, "/bin/echo"); |  | ||||||
|         assert_eq!(config.auto_restart, true); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,434 +0,0 @@ | |||||||
| use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus}; |  | ||||||
| use std::fs; |  | ||||||
| use std::path::PathBuf; |  | ||||||
| use std::process::Command; |  | ||||||
|  |  | ||||||
| #[derive(Debug)] |  | ||||||
| pub struct SystemdServiceManager { |  | ||||||
|     service_prefix: String, |  | ||||||
|     user_mode: bool, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl SystemdServiceManager { |  | ||||||
|     pub fn new() -> Self { |  | ||||||
|         Self { |  | ||||||
|             service_prefix: "sal".to_string(), |  | ||||||
|             user_mode: true, // Default to user services for safety |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     pub fn new_system() -> Self { |  | ||||||
|         Self { |  | ||||||
|             service_prefix: "sal".to_string(), |  | ||||||
|             user_mode: false, // System-wide services (requires root) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn get_service_name(&self, service_name: &str) -> String { |  | ||||||
|         format!("{}-{}.service", self.service_prefix, service_name) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn get_unit_file_path(&self, service_name: &str) -> PathBuf { |  | ||||||
|         let service_file = self.get_service_name(service_name); |  | ||||||
|         if self.user_mode { |  | ||||||
|             // User service directory |  | ||||||
|             let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); |  | ||||||
|             PathBuf::from(home) |  | ||||||
|                 .join(".config") |  | ||||||
|                 .join("systemd") |  | ||||||
|                 .join("user") |  | ||||||
|                 .join(service_file) |  | ||||||
|         } else { |  | ||||||
|             // System service directory |  | ||||||
|             PathBuf::from("/etc/systemd/system").join(service_file) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn run_systemctl(&self, args: &[&str]) -> Result<String, ServiceManagerError> { |  | ||||||
|         let mut cmd = Command::new("systemctl"); |  | ||||||
|  |  | ||||||
|         if self.user_mode { |  | ||||||
|             cmd.arg("--user"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         cmd.args(args); |  | ||||||
|  |  | ||||||
|         let output = cmd |  | ||||||
|             .output() |  | ||||||
|             .map_err(|e| ServiceManagerError::Other(format!("Failed to run systemctl: {}", e)))?; |  | ||||||
|  |  | ||||||
|         if !output.status.success() { |  | ||||||
|             let stderr = String::from_utf8_lossy(&output.stderr); |  | ||||||
|             return Err(ServiceManagerError::Other(format!( |  | ||||||
|                 "systemctl command failed: {}", |  | ||||||
|                 stderr |  | ||||||
|             ))); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Ok(String::from_utf8_lossy(&output.stdout).to_string()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn create_unit_file(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { |  | ||||||
|         let unit_path = self.get_unit_file_path(&config.name); |  | ||||||
|  |  | ||||||
|         // Ensure the directory exists |  | ||||||
|         if let Some(parent) = unit_path.parent() { |  | ||||||
|             fs::create_dir_all(parent).map_err(|e| { |  | ||||||
|                 ServiceManagerError::Other(format!("Failed to create unit directory: {}", e)) |  | ||||||
|             })?; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Create the unit file content |  | ||||||
|         let mut unit_content = String::new(); |  | ||||||
|         unit_content.push_str("[Unit]\n"); |  | ||||||
|         unit_content.push_str(&format!("Description={} service\n", config.name)); |  | ||||||
|         unit_content.push_str("After=network.target\n\n"); |  | ||||||
|  |  | ||||||
|         unit_content.push_str("[Service]\n"); |  | ||||||
|         unit_content.push_str("Type=simple\n"); |  | ||||||
|  |  | ||||||
|         // Build the ExecStart command |  | ||||||
|         let mut exec_start = config.binary_path.clone(); |  | ||||||
|         for arg in &config.args { |  | ||||||
|             exec_start.push(' '); |  | ||||||
|             exec_start.push_str(arg); |  | ||||||
|         } |  | ||||||
|         unit_content.push_str(&format!("ExecStart={}\n", exec_start)); |  | ||||||
|  |  | ||||||
|         if let Some(working_dir) = &config.working_directory { |  | ||||||
|             unit_content.push_str(&format!("WorkingDirectory={}\n", working_dir)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Add environment variables |  | ||||||
|         for (key, value) in &config.environment { |  | ||||||
|             unit_content.push_str(&format!("Environment=\"{}={}\"\n", key, value)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if config.auto_restart { |  | ||||||
|             unit_content.push_str("Restart=always\n"); |  | ||||||
|             unit_content.push_str("RestartSec=5\n"); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         unit_content.push_str("\n[Install]\n"); |  | ||||||
|         unit_content.push_str("WantedBy=default.target\n"); |  | ||||||
|  |  | ||||||
|         // Write the unit file |  | ||||||
|         fs::write(&unit_path, unit_content) |  | ||||||
|             .map_err(|e| ServiceManagerError::Other(format!("Failed to write unit file: {}", e)))?; |  | ||||||
|  |  | ||||||
|         // Reload systemd to pick up the new unit file |  | ||||||
|         self.run_systemctl(&["daemon-reload"])?; |  | ||||||
|  |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl ServiceManager for SystemdServiceManager { |  | ||||||
|     fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError> { |  | ||||||
|         let unit_path = self.get_unit_file_path(service_name); |  | ||||||
|         Ok(unit_path.exists()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { |  | ||||||
|         let service_name = self.get_service_name(&config.name); |  | ||||||
|  |  | ||||||
|         // Check if service already exists and is running |  | ||||||
|         if self.exists(&config.name)? { |  | ||||||
|             match self.status(&config.name)? { |  | ||||||
|                 ServiceStatus::Running => { |  | ||||||
|                     return Err(ServiceManagerError::ServiceAlreadyExists( |  | ||||||
|                         config.name.clone(), |  | ||||||
|                     )); |  | ||||||
|                 } |  | ||||||
|                 _ => { |  | ||||||
|                     // Service exists but not running, we can start it |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             // Create the unit file |  | ||||||
|             self.create_unit_file(config)?; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Enable and start the service |  | ||||||
|         self.run_systemctl(&["enable", &service_name]) |  | ||||||
|             .map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?; |  | ||||||
|  |  | ||||||
|         self.run_systemctl(&["start", &service_name]) |  | ||||||
|             .map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?; |  | ||||||
|  |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         let service_unit = self.get_service_name(service_name); |  | ||||||
|  |  | ||||||
|         // Check if unit file exists |  | ||||||
|         if !self.exists(service_name)? { |  | ||||||
|             return Err(ServiceManagerError::ServiceNotFound( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|             )); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Check if already running |  | ||||||
|         match self.status(service_name)? { |  | ||||||
|             ServiceStatus::Running => { |  | ||||||
|                 return Ok(()); // Already running, nothing to do |  | ||||||
|             } |  | ||||||
|             _ => { |  | ||||||
|                 // Start the service |  | ||||||
|                 self.run_systemctl(&["start", &service_unit]).map_err(|e| { |  | ||||||
|                     ServiceManagerError::StartFailed(service_name.to_string(), e.to_string()) |  | ||||||
|                 })?; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start_and_confirm( |  | ||||||
|         &self, |  | ||||||
|         config: &ServiceConfig, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<(), ServiceManagerError> { |  | ||||||
|         // Start the service first |  | ||||||
|         self.start(config)?; |  | ||||||
|  |  | ||||||
|         // Wait for confirmation with timeout |  | ||||||
|         let start_time = std::time::Instant::now(); |  | ||||||
|         let timeout_duration = std::time::Duration::from_secs(timeout_secs); |  | ||||||
|  |  | ||||||
|         while start_time.elapsed() < timeout_duration { |  | ||||||
|             match self.status(&config.name) { |  | ||||||
|                 Ok(ServiceStatus::Running) => return Ok(()), |  | ||||||
|                 Ok(ServiceStatus::Failed) => { |  | ||||||
|                     return Err(ServiceManagerError::StartFailed( |  | ||||||
|                         config.name.clone(), |  | ||||||
|                         "Service failed to start".to_string(), |  | ||||||
|                     )); |  | ||||||
|                 } |  | ||||||
|                 Ok(_) => { |  | ||||||
|                     // Still starting, wait a bit |  | ||||||
|                     std::thread::sleep(std::time::Duration::from_millis(100)); |  | ||||||
|                 } |  | ||||||
|                 Err(_) => { |  | ||||||
|                     // Service might not exist yet, wait a bit |  | ||||||
|                     std::thread::sleep(std::time::Duration::from_millis(100)); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Err(ServiceManagerError::StartFailed( |  | ||||||
|             config.name.clone(), |  | ||||||
|             format!("Service did not start within {} seconds", timeout_secs), |  | ||||||
|         )) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start_existing_and_confirm( |  | ||||||
|         &self, |  | ||||||
|         service_name: &str, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<(), ServiceManagerError> { |  | ||||||
|         // Start the existing service first |  | ||||||
|         self.start_existing(service_name)?; |  | ||||||
|  |  | ||||||
|         // Wait for confirmation with timeout |  | ||||||
|         let start_time = std::time::Instant::now(); |  | ||||||
|         let timeout_duration = std::time::Duration::from_secs(timeout_secs); |  | ||||||
|  |  | ||||||
|         while start_time.elapsed() < timeout_duration { |  | ||||||
|             match self.status(service_name) { |  | ||||||
|                 Ok(ServiceStatus::Running) => return Ok(()), |  | ||||||
|                 Ok(ServiceStatus::Failed) => { |  | ||||||
|                     return Err(ServiceManagerError::StartFailed( |  | ||||||
|                         service_name.to_string(), |  | ||||||
|                         "Service failed to start".to_string(), |  | ||||||
|                     )); |  | ||||||
|                 } |  | ||||||
|                 Ok(_) => { |  | ||||||
|                     // Still starting, wait a bit |  | ||||||
|                     std::thread::sleep(std::time::Duration::from_millis(100)); |  | ||||||
|                 } |  | ||||||
|                 Err(_) => { |  | ||||||
|                     // Service might not exist yet, wait a bit |  | ||||||
|                     std::thread::sleep(std::time::Duration::from_millis(100)); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Err(ServiceManagerError::StartFailed( |  | ||||||
|             service_name.to_string(), |  | ||||||
|             format!("Service did not start within {} seconds", timeout_secs), |  | ||||||
|         )) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         let service_unit = self.get_service_name(service_name); |  | ||||||
|  |  | ||||||
|         // Check if service exists |  | ||||||
|         if !self.exists(service_name)? { |  | ||||||
|             return Err(ServiceManagerError::ServiceNotFound( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|             )); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Stop the service |  | ||||||
|         self.run_systemctl(&["stop", &service_unit]).map_err(|e| { |  | ||||||
|             ServiceManagerError::StopFailed(service_name.to_string(), e.to_string()) |  | ||||||
|         })?; |  | ||||||
|  |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         let service_unit = self.get_service_name(service_name); |  | ||||||
|  |  | ||||||
|         // Check if service exists |  | ||||||
|         if !self.exists(service_name)? { |  | ||||||
|             return Err(ServiceManagerError::ServiceNotFound( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|             )); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Restart the service |  | ||||||
|         self.run_systemctl(&["restart", &service_unit]) |  | ||||||
|             .map_err(|e| { |  | ||||||
|                 ServiceManagerError::RestartFailed(service_name.to_string(), e.to_string()) |  | ||||||
|             })?; |  | ||||||
|  |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> { |  | ||||||
|         let service_unit = self.get_service_name(service_name); |  | ||||||
|  |  | ||||||
|         // Check if service exists |  | ||||||
|         if !self.exists(service_name)? { |  | ||||||
|             return Err(ServiceManagerError::ServiceNotFound( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|             )); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Get service status |  | ||||||
|         let output = self |  | ||||||
|             .run_systemctl(&["is-active", &service_unit]) |  | ||||||
|             .unwrap_or_else(|_| "unknown".to_string()); |  | ||||||
|  |  | ||||||
|         let status = match output.trim() { |  | ||||||
|             "active" => ServiceStatus::Running, |  | ||||||
|             "inactive" => ServiceStatus::Stopped, |  | ||||||
|             "failed" => ServiceStatus::Failed, |  | ||||||
|             _ => ServiceStatus::Unknown, |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         Ok(status) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn logs( |  | ||||||
|         &self, |  | ||||||
|         service_name: &str, |  | ||||||
|         lines: Option<usize>, |  | ||||||
|     ) -> Result<String, ServiceManagerError> { |  | ||||||
|         let service_unit = self.get_service_name(service_name); |  | ||||||
|  |  | ||||||
|         // Check if service exists |  | ||||||
|         if !self.exists(service_name)? { |  | ||||||
|             return Err(ServiceManagerError::ServiceNotFound( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|             )); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Build journalctl command |  | ||||||
|         let mut args = vec!["--unit", &service_unit, "--no-pager"]; |  | ||||||
|         let lines_arg; |  | ||||||
|         if let Some(n) = lines { |  | ||||||
|             lines_arg = format!("--lines={}", n); |  | ||||||
|             args.push(&lines_arg); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Use journalctl to get logs |  | ||||||
|         let mut cmd = std::process::Command::new("journalctl"); |  | ||||||
|         if self.user_mode { |  | ||||||
|             cmd.arg("--user"); |  | ||||||
|         } |  | ||||||
|         cmd.args(&args); |  | ||||||
|  |  | ||||||
|         let output = cmd.output().map_err(|e| { |  | ||||||
|             ServiceManagerError::LogsFailed( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|                 format!("Failed to run journalctl: {}", e), |  | ||||||
|             ) |  | ||||||
|         })?; |  | ||||||
|  |  | ||||||
|         if !output.status.success() { |  | ||||||
|             let stderr = String::from_utf8_lossy(&output.stderr); |  | ||||||
|             return Err(ServiceManagerError::LogsFailed( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|                 format!("journalctl command failed: {}", stderr), |  | ||||||
|             )); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Ok(String::from_utf8_lossy(&output.stdout).to_string()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn list(&self) -> Result<Vec<String>, ServiceManagerError> { |  | ||||||
|         // List all services with our prefix |  | ||||||
|         let output = |  | ||||||
|             self.run_systemctl(&["list-units", "--type=service", "--all", "--no-pager"])?; |  | ||||||
|  |  | ||||||
|         let mut services = Vec::new(); |  | ||||||
|         for line in output.lines() { |  | ||||||
|             if line.contains(&format!("{}-", self.service_prefix)) { |  | ||||||
|                 // Extract service name from the line |  | ||||||
|                 if let Some(unit_name) = line.split_whitespace().next() { |  | ||||||
|                     if let Some(service_name) = unit_name.strip_suffix(".service") { |  | ||||||
|                         if let Some(name) = |  | ||||||
|                             service_name.strip_prefix(&format!("{}-", self.service_prefix)) |  | ||||||
|                         { |  | ||||||
|                             services.push(name.to_string()); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Ok(services) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         let service_unit = self.get_service_name(service_name); |  | ||||||
|  |  | ||||||
|         // Check if service exists |  | ||||||
|         if !self.exists(service_name)? { |  | ||||||
|             return Err(ServiceManagerError::ServiceNotFound( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|             )); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Try to stop the service first, but don't fail if it's already stopped |  | ||||||
|         if let Err(e) = self.stop(service_name) { |  | ||||||
|             log::warn!( |  | ||||||
|                 "Failed to stop service '{}' before removal: {}", |  | ||||||
|                 service_name, |  | ||||||
|                 e |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Disable the service |  | ||||||
|         if let Err(e) = self.run_systemctl(&["disable", &service_unit]) { |  | ||||||
|             log::warn!("Failed to disable service '{}': {}", service_name, e); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Remove the unit file |  | ||||||
|         let unit_path = self.get_unit_file_path(service_name); |  | ||||||
|         if unit_path.exists() { |  | ||||||
|             std::fs::remove_file(&unit_path).map_err(|e| { |  | ||||||
|                 ServiceManagerError::Other(format!("Failed to remove unit file: {}", e)) |  | ||||||
|             })?; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Reload systemd to pick up the changes |  | ||||||
|         self.run_systemctl(&["daemon-reload"])?; |  | ||||||
|  |  | ||||||
|         Ok(()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,379 +0,0 @@ | |||||||
| use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus}; |  | ||||||
| use once_cell::sync::Lazy; |  | ||||||
| use serde_json::json; |  | ||||||
| use std::sync::Arc; |  | ||||||
| use std::time::Duration; |  | ||||||
| use tokio::runtime::Runtime; |  | ||||||
| use tokio::time::timeout; |  | ||||||
| use zinit_client::{ServiceStatus as ZinitServiceStatus, ZinitClient, ZinitError}; |  | ||||||
|  |  | ||||||
| // Shared runtime for async operations - production-safe initialization |  | ||||||
| static ASYNC_RUNTIME: Lazy<Option<Runtime>> = Lazy::new(|| Runtime::new().ok()); |  | ||||||
|  |  | ||||||
| /// Get the async runtime, creating a temporary one if the static runtime failed |  | ||||||
| fn get_runtime() -> Result<Runtime, ServiceManagerError> { |  | ||||||
|     // Try to use the static runtime first |  | ||||||
|     if let Some(_runtime) = ASYNC_RUNTIME.as_ref() { |  | ||||||
|         // We can't return a reference to the static runtime because we need ownership |  | ||||||
|         // for block_on, so we create a new one. This is a reasonable trade-off for safety. |  | ||||||
|         Runtime::new().map_err(|e| { |  | ||||||
|             ServiceManagerError::Other(format!("Failed to create async runtime: {}", e)) |  | ||||||
|         }) |  | ||||||
|     } else { |  | ||||||
|         // Static runtime failed, try to create a new one |  | ||||||
|         Runtime::new().map_err(|e| { |  | ||||||
|             ServiceManagerError::Other(format!("Failed to create async runtime: {}", e)) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| pub struct ZinitServiceManager { |  | ||||||
|     client: Arc<ZinitClient>, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl ZinitServiceManager { |  | ||||||
|     pub fn new(socket_path: &str) -> Result<Self, ServiceManagerError> { |  | ||||||
|         // Create the base zinit client directly |  | ||||||
|         let client = Arc::new(ZinitClient::new(socket_path)); |  | ||||||
|  |  | ||||||
|         Ok(ZinitServiceManager { client }) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Execute an async operation using the shared runtime or current context |  | ||||||
|     fn execute_async<F, T>(&self, operation: F) -> Result<T, ServiceManagerError> |  | ||||||
|     where |  | ||||||
|         F: std::future::Future<Output = Result<T, ZinitError>> + Send + 'static, |  | ||||||
|         T: Send + 'static, |  | ||||||
|     { |  | ||||||
|         // Check if we're already in a tokio runtime context |  | ||||||
|         if let Ok(_handle) = tokio::runtime::Handle::try_current() { |  | ||||||
|             // We're in an async context, use spawn_blocking to avoid nested runtime |  | ||||||
|             let result = std::thread::spawn( |  | ||||||
|                 move || -> Result<Result<T, ZinitError>, ServiceManagerError> { |  | ||||||
|                     let rt = Runtime::new().map_err(|e| { |  | ||||||
|                         ServiceManagerError::Other(format!("Failed to create runtime: {}", e)) |  | ||||||
|                     })?; |  | ||||||
|                     Ok(rt.block_on(operation)) |  | ||||||
|                 }, |  | ||||||
|             ) |  | ||||||
|             .join() |  | ||||||
|             .map_err(|_| ServiceManagerError::Other("Thread join failed".to_string()))?; |  | ||||||
|             result?.map_err(|e| ServiceManagerError::Other(e.to_string())) |  | ||||||
|         } else { |  | ||||||
|             // No current runtime, use production-safe runtime |  | ||||||
|             let runtime = get_runtime()?; |  | ||||||
|             runtime |  | ||||||
|                 .block_on(operation) |  | ||||||
|                 .map_err(|e| ServiceManagerError::Other(e.to_string())) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /// Execute an async operation with timeout using the shared runtime or current context |  | ||||||
|     fn execute_async_with_timeout<F, T>( |  | ||||||
|         &self, |  | ||||||
|         operation: F, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<T, ServiceManagerError> |  | ||||||
|     where |  | ||||||
|         F: std::future::Future<Output = Result<T, ZinitError>> + Send + 'static, |  | ||||||
|         T: Send + 'static, |  | ||||||
|     { |  | ||||||
|         let timeout_duration = Duration::from_secs(timeout_secs); |  | ||||||
|         let timeout_op = timeout(timeout_duration, operation); |  | ||||||
|  |  | ||||||
|         // Check if we're already in a tokio runtime context |  | ||||||
|         if let Ok(_handle) = tokio::runtime::Handle::try_current() { |  | ||||||
|             // We're in an async context, use spawn_blocking to avoid nested runtime |  | ||||||
|             let result = std::thread::spawn(move || { |  | ||||||
|                 let rt = tokio::runtime::Runtime::new().unwrap(); |  | ||||||
|                 rt.block_on(timeout_op) |  | ||||||
|             }) |  | ||||||
|             .join() |  | ||||||
|             .map_err(|_| ServiceManagerError::Other("Thread join failed".to_string()))?; |  | ||||||
|  |  | ||||||
|             result |  | ||||||
|                 .map_err(|_| { |  | ||||||
|                     ServiceManagerError::Other(format!( |  | ||||||
|                         "Operation timed out after {} seconds", |  | ||||||
|                         timeout_secs |  | ||||||
|                     )) |  | ||||||
|                 })? |  | ||||||
|                 .map_err(|e| ServiceManagerError::Other(e.to_string())) |  | ||||||
|         } else { |  | ||||||
|             // No current runtime, use production-safe runtime |  | ||||||
|             let runtime = get_runtime()?; |  | ||||||
|             runtime |  | ||||||
|                 .block_on(timeout_op) |  | ||||||
|                 .map_err(|_| { |  | ||||||
|                     ServiceManagerError::Other(format!( |  | ||||||
|                         "Operation timed out after {} seconds", |  | ||||||
|                         timeout_secs |  | ||||||
|                     )) |  | ||||||
|                 })? |  | ||||||
|                 .map_err(|e| ServiceManagerError::Other(e.to_string())) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl ServiceManager for ZinitServiceManager { |  | ||||||
|     fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError> { |  | ||||||
|         let status_res = self.status(service_name); |  | ||||||
|         match status_res { |  | ||||||
|             Ok(_) => Ok(true), |  | ||||||
|             Err(ServiceManagerError::ServiceNotFound(_)) => Ok(false), |  | ||||||
|             Err(e) => Err(e), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> { |  | ||||||
|         // Build the exec command with args |  | ||||||
|         let mut exec_command = config.binary_path.clone(); |  | ||||||
|         if !config.args.is_empty() { |  | ||||||
|             exec_command.push(' '); |  | ||||||
|             exec_command.push_str(&config.args.join(" ")); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Create zinit-compatible service configuration |  | ||||||
|         let mut service_config = json!({ |  | ||||||
|             "exec": exec_command, |  | ||||||
|             "oneshot": !config.auto_restart,  // zinit uses oneshot, not restart |  | ||||||
|             "env": config.environment, |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         // Add optional fields if present |  | ||||||
|         if let Some(ref working_dir) = config.working_directory { |  | ||||||
|             // Zinit doesn't support working_directory directly, so we need to modify the exec command |  | ||||||
|             let cd_command = format!("cd {} && {}", working_dir, exec_command); |  | ||||||
|             service_config["exec"] = json!(cd_command); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         let client = Arc::clone(&self.client); |  | ||||||
|         let service_name = config.name.clone(); |  | ||||||
|         self.execute_async( |  | ||||||
|             async move { client.create_service(&service_name, service_config).await }, |  | ||||||
|         ) |  | ||||||
|         .map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?; |  | ||||||
|  |  | ||||||
|         self.start_existing(&config.name) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         let client = Arc::clone(&self.client); |  | ||||||
|         let service_name_owned = service_name.to_string(); |  | ||||||
|         let service_name_for_error = service_name.to_string(); |  | ||||||
|         self.execute_async(async move { client.start(&service_name_owned).await }) |  | ||||||
|             .map_err(|e| ServiceManagerError::StartFailed(service_name_for_error, e.to_string())) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start_and_confirm( |  | ||||||
|         &self, |  | ||||||
|         config: &ServiceConfig, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<(), ServiceManagerError> { |  | ||||||
|         // Start the service first |  | ||||||
|         self.start(config)?; |  | ||||||
|  |  | ||||||
|         // Wait for confirmation with timeout using the shared runtime |  | ||||||
|         self.execute_async_with_timeout( |  | ||||||
|             async move { |  | ||||||
|                 let start_time = std::time::Instant::now(); |  | ||||||
|                 let timeout_duration = Duration::from_secs(timeout_secs); |  | ||||||
|  |  | ||||||
|                 while start_time.elapsed() < timeout_duration { |  | ||||||
|                     // We need to call status in a blocking way from within the async context |  | ||||||
|                     // For now, we'll use a simple polling approach |  | ||||||
|                     tokio::time::sleep(Duration::from_millis(100)).await; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Return a timeout error that will be handled by execute_async_with_timeout |  | ||||||
|                 // Use a generic error since we don't know the exact ZinitError variants |  | ||||||
|                 Err(ZinitError::from(std::io::Error::new( |  | ||||||
|                     std::io::ErrorKind::TimedOut, |  | ||||||
|                     "Timeout waiting for service confirmation", |  | ||||||
|                 ))) |  | ||||||
|             }, |  | ||||||
|             timeout_secs, |  | ||||||
|         )?; |  | ||||||
|  |  | ||||||
|         // Check final status |  | ||||||
|         match self.status(&config.name)? { |  | ||||||
|             ServiceStatus::Running => Ok(()), |  | ||||||
|             ServiceStatus::Failed => Err(ServiceManagerError::StartFailed( |  | ||||||
|                 config.name.clone(), |  | ||||||
|                 "Service failed to start".to_string(), |  | ||||||
|             )), |  | ||||||
|             _ => Err(ServiceManagerError::StartFailed( |  | ||||||
|                 config.name.clone(), |  | ||||||
|                 format!("Service did not start within {} seconds", timeout_secs), |  | ||||||
|             )), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn start_existing_and_confirm( |  | ||||||
|         &self, |  | ||||||
|         service_name: &str, |  | ||||||
|         timeout_secs: u64, |  | ||||||
|     ) -> Result<(), ServiceManagerError> { |  | ||||||
|         // Start the existing service first |  | ||||||
|         self.start_existing(service_name)?; |  | ||||||
|  |  | ||||||
|         // Wait for confirmation with timeout using the shared runtime |  | ||||||
|         self.execute_async_with_timeout( |  | ||||||
|             async move { |  | ||||||
|                 let start_time = std::time::Instant::now(); |  | ||||||
|                 let timeout_duration = Duration::from_secs(timeout_secs); |  | ||||||
|  |  | ||||||
|                 while start_time.elapsed() < timeout_duration { |  | ||||||
|                     tokio::time::sleep(Duration::from_millis(100)).await; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Return a timeout error that will be handled by execute_async_with_timeout |  | ||||||
|                 // Use a generic error since we don't know the exact ZinitError variants |  | ||||||
|                 Err(ZinitError::from(std::io::Error::new( |  | ||||||
|                     std::io::ErrorKind::TimedOut, |  | ||||||
|                     "Timeout waiting for service confirmation", |  | ||||||
|                 ))) |  | ||||||
|             }, |  | ||||||
|             timeout_secs, |  | ||||||
|         )?; |  | ||||||
|  |  | ||||||
|         // Check final status |  | ||||||
|         match self.status(service_name)? { |  | ||||||
|             ServiceStatus::Running => Ok(()), |  | ||||||
|             ServiceStatus::Failed => Err(ServiceManagerError::StartFailed( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|                 "Service failed to start".to_string(), |  | ||||||
|             )), |  | ||||||
|             _ => Err(ServiceManagerError::StartFailed( |  | ||||||
|                 service_name.to_string(), |  | ||||||
|                 format!("Service did not start within {} seconds", timeout_secs), |  | ||||||
|             )), |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         let client = Arc::clone(&self.client); |  | ||||||
|         let service_name_owned = service_name.to_string(); |  | ||||||
|         let service_name_for_error = service_name.to_string(); |  | ||||||
|         self.execute_async(async move { client.stop(&service_name_owned).await }) |  | ||||||
|             .map_err(|e| ServiceManagerError::StopFailed(service_name_for_error, e.to_string())) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         let client = Arc::clone(&self.client); |  | ||||||
|         let service_name_owned = service_name.to_string(); |  | ||||||
|         let service_name_for_error = service_name.to_string(); |  | ||||||
|         self.execute_async(async move { client.restart(&service_name_owned).await }) |  | ||||||
|             .map_err(|e| ServiceManagerError::RestartFailed(service_name_for_error, e.to_string())) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> { |  | ||||||
|         let client = Arc::clone(&self.client); |  | ||||||
|         let service_name_owned = service_name.to_string(); |  | ||||||
|         let service_name_for_error = service_name.to_string(); |  | ||||||
|         let status: ZinitServiceStatus = self |  | ||||||
|             .execute_async(async move { client.status(&service_name_owned).await }) |  | ||||||
|             .map_err(|e| { |  | ||||||
|                 // Check if this is a "service not found" error |  | ||||||
|                 if e.to_string().contains("not found") || e.to_string().contains("does not exist") { |  | ||||||
|                     ServiceManagerError::ServiceNotFound(service_name_for_error) |  | ||||||
|                 } else { |  | ||||||
|                     ServiceManagerError::Other(e.to_string()) |  | ||||||
|                 } |  | ||||||
|             })?; |  | ||||||
|  |  | ||||||
|         // ServiceStatus is a struct with fields, not an enum |  | ||||||
|         // We need to check the state field to determine the status |  | ||||||
|         // Convert ServiceState to string and match on that |  | ||||||
|         let state_str = format!("{:?}", status.state).to_lowercase(); |  | ||||||
|         let service_status = match state_str.as_str() { |  | ||||||
|             s if s.contains("running") => crate::ServiceStatus::Running, |  | ||||||
|             s if s.contains("stopped") => crate::ServiceStatus::Stopped, |  | ||||||
|             s if s.contains("failed") => crate::ServiceStatus::Failed, |  | ||||||
|             _ => crate::ServiceStatus::Unknown, |  | ||||||
|         }; |  | ||||||
|         Ok(service_status) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn logs( |  | ||||||
|         &self, |  | ||||||
|         service_name: &str, |  | ||||||
|         _lines: Option<usize>, |  | ||||||
|     ) -> Result<String, ServiceManagerError> { |  | ||||||
|         // The logs method takes (follow: bool, filter: Option<impl AsRef<str>>) |  | ||||||
|         let client = Arc::clone(&self.client); |  | ||||||
|         let service_name_owned = service_name.to_string(); |  | ||||||
|         let logs = self |  | ||||||
|             .execute_async(async move { |  | ||||||
|                 use futures::StreamExt; |  | ||||||
|                 use tokio::time::{timeout, Duration}; |  | ||||||
|  |  | ||||||
|                 let mut log_stream = client |  | ||||||
|                     .logs(false, Some(service_name_owned.as_str())) |  | ||||||
|                     .await?; |  | ||||||
|                 let mut logs = Vec::new(); |  | ||||||
|  |  | ||||||
|                 // Collect logs from the stream with a reasonable limit |  | ||||||
|                 let mut count = 0; |  | ||||||
|                 const MAX_LOGS: usize = 100; |  | ||||||
|                 const LOG_TIMEOUT: Duration = Duration::from_secs(5); |  | ||||||
|  |  | ||||||
|                 // Use timeout to prevent hanging |  | ||||||
|                 let result = timeout(LOG_TIMEOUT, async { |  | ||||||
|                     while let Some(log_result) = log_stream.next().await { |  | ||||||
|                         match log_result { |  | ||||||
|                             Ok(log_entry) => { |  | ||||||
|                                 logs.push(format!("{:?}", log_entry)); |  | ||||||
|                                 count += 1; |  | ||||||
|                                 if count >= MAX_LOGS { |  | ||||||
|                                     break; |  | ||||||
|                                 } |  | ||||||
|                             } |  | ||||||
|                             Err(_) => break, |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 }) |  | ||||||
|                 .await; |  | ||||||
|  |  | ||||||
|                 // Handle timeout - this is not an error, just means no more logs available |  | ||||||
|                 if result.is_err() { |  | ||||||
|                     log::debug!( |  | ||||||
|                         "Log reading timed out after {} seconds, returning {} logs", |  | ||||||
|                         LOG_TIMEOUT.as_secs(), |  | ||||||
|                         logs.len() |  | ||||||
|                     ); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 Ok::<Vec<String>, ZinitError>(logs) |  | ||||||
|             }) |  | ||||||
|             .map_err(|e| { |  | ||||||
|                 ServiceManagerError::LogsFailed(service_name.to_string(), e.to_string()) |  | ||||||
|             })?; |  | ||||||
|         Ok(logs.join("\n")) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn list(&self) -> Result<Vec<String>, ServiceManagerError> { |  | ||||||
|         let client = Arc::clone(&self.client); |  | ||||||
|         let services = self |  | ||||||
|             .execute_async(async move { client.list().await }) |  | ||||||
|             .map_err(|e| ServiceManagerError::Other(e.to_string()))?; |  | ||||||
|         Ok(services.keys().cloned().collect()) |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> { |  | ||||||
|         // Try to stop the service first, but don't fail if it's already stopped or doesn't exist |  | ||||||
|         if let Err(e) = self.stop(service_name) { |  | ||||||
|             // Log the error but continue with removal |  | ||||||
|             log::warn!( |  | ||||||
|                 "Failed to stop service '{}' before removal: {}", |  | ||||||
|                 service_name, |  | ||||||
|                 e |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         let client = Arc::clone(&self.client); |  | ||||||
|         let service_name = service_name.to_string(); |  | ||||||
|         self.execute_async(async move { client.delete_service(&service_name).await }) |  | ||||||
|             .map_err(|e| ServiceManagerError::Other(e.to_string())) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,243 +0,0 @@ | |||||||
| use sal_service_manager::{create_service_manager, ServiceConfig, ServiceManager}; |  | ||||||
| use std::collections::HashMap; |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_create_service_manager() { |  | ||||||
|     // Test that the factory function creates the appropriate service manager for the platform |  | ||||||
|     let manager = create_service_manager().expect("Failed to create service manager"); |  | ||||||
|  |  | ||||||
|     // Test basic functionality - should be able to call methods without panicking |  | ||||||
|     let list_result = manager.list(); |  | ||||||
|  |  | ||||||
|     // The result might be an error (if no service system is available), but it shouldn't panic |  | ||||||
|     match list_result { |  | ||||||
|         Ok(services) => { |  | ||||||
|             println!( |  | ||||||
|                 "✓ Service manager created successfully, found {} services", |  | ||||||
|                 services.len() |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             println!("✓ Service manager created, but got expected error: {}", e); |  | ||||||
|             // This is expected on systems without the appropriate service manager |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_service_config_creation() { |  | ||||||
|     // Test creating various service configurations |  | ||||||
|     let basic_config = ServiceConfig { |  | ||||||
|         name: "test-service".to_string(), |  | ||||||
|         binary_path: "/usr/bin/echo".to_string(), |  | ||||||
|         args: vec!["hello".to_string(), "world".to_string()], |  | ||||||
|         working_directory: None, |  | ||||||
|         environment: HashMap::new(), |  | ||||||
|         auto_restart: false, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     assert_eq!(basic_config.name, "test-service"); |  | ||||||
|     assert_eq!(basic_config.binary_path, "/usr/bin/echo"); |  | ||||||
|     assert_eq!(basic_config.args.len(), 2); |  | ||||||
|     assert_eq!(basic_config.args[0], "hello"); |  | ||||||
|     assert_eq!(basic_config.args[1], "world"); |  | ||||||
|     assert!(basic_config.working_directory.is_none()); |  | ||||||
|     assert!(basic_config.environment.is_empty()); |  | ||||||
|     assert!(!basic_config.auto_restart); |  | ||||||
|  |  | ||||||
|     println!("✓ Basic service config created successfully"); |  | ||||||
|  |  | ||||||
|     // Test config with environment variables |  | ||||||
|     let mut env = HashMap::new(); |  | ||||||
|     env.insert("PATH".to_string(), "/usr/bin:/bin".to_string()); |  | ||||||
|     env.insert("HOME".to_string(), "/tmp".to_string()); |  | ||||||
|  |  | ||||||
|     let env_config = ServiceConfig { |  | ||||||
|         name: "env-service".to_string(), |  | ||||||
|         binary_path: "/usr/bin/env".to_string(), |  | ||||||
|         args: vec![], |  | ||||||
|         working_directory: Some("/tmp".to_string()), |  | ||||||
|         environment: env.clone(), |  | ||||||
|         auto_restart: true, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     assert_eq!(env_config.name, "env-service"); |  | ||||||
|     assert_eq!(env_config.binary_path, "/usr/bin/env"); |  | ||||||
|     assert!(env_config.args.is_empty()); |  | ||||||
|     assert_eq!(env_config.working_directory, Some("/tmp".to_string())); |  | ||||||
|     assert_eq!(env_config.environment.len(), 2); |  | ||||||
|     assert_eq!( |  | ||||||
|         env_config.environment.get("PATH"), |  | ||||||
|         Some(&"/usr/bin:/bin".to_string()) |  | ||||||
|     ); |  | ||||||
|     assert_eq!( |  | ||||||
|         env_config.environment.get("HOME"), |  | ||||||
|         Some(&"/tmp".to_string()) |  | ||||||
|     ); |  | ||||||
|     assert!(env_config.auto_restart); |  | ||||||
|  |  | ||||||
|     println!("✓ Environment service config created successfully"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_service_config_clone() { |  | ||||||
|     // Test that ServiceConfig can be cloned |  | ||||||
|     let original_config = ServiceConfig { |  | ||||||
|         name: "original".to_string(), |  | ||||||
|         binary_path: "/bin/sh".to_string(), |  | ||||||
|         args: vec!["-c".to_string(), "echo test".to_string()], |  | ||||||
|         working_directory: Some("/home".to_string()), |  | ||||||
|         environment: { |  | ||||||
|             let mut env = HashMap::new(); |  | ||||||
|             env.insert("TEST".to_string(), "value".to_string()); |  | ||||||
|             env |  | ||||||
|         }, |  | ||||||
|         auto_restart: true, |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     let cloned_config = original_config.clone(); |  | ||||||
|  |  | ||||||
|     assert_eq!(original_config.name, cloned_config.name); |  | ||||||
|     assert_eq!(original_config.binary_path, cloned_config.binary_path); |  | ||||||
|     assert_eq!(original_config.args, cloned_config.args); |  | ||||||
|     assert_eq!( |  | ||||||
|         original_config.working_directory, |  | ||||||
|         cloned_config.working_directory |  | ||||||
|     ); |  | ||||||
|     assert_eq!(original_config.environment, cloned_config.environment); |  | ||||||
|     assert_eq!(original_config.auto_restart, cloned_config.auto_restart); |  | ||||||
|  |  | ||||||
|     println!("✓ Service config cloning works correctly"); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(target_os = "macos")] |  | ||||||
| #[test] |  | ||||||
| fn test_macos_service_manager() { |  | ||||||
|     use sal_service_manager::LaunchctlServiceManager; |  | ||||||
|  |  | ||||||
|     // Test creating macOS-specific service manager |  | ||||||
|     let manager = LaunchctlServiceManager::new(); |  | ||||||
|  |  | ||||||
|     // Test basic functionality |  | ||||||
|     let list_result = manager.list(); |  | ||||||
|     match list_result { |  | ||||||
|         Ok(services) => { |  | ||||||
|             println!( |  | ||||||
|                 "✓ macOS LaunchctlServiceManager created successfully, found {} services", |  | ||||||
|                 services.len() |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             println!( |  | ||||||
|                 "✓ macOS LaunchctlServiceManager created, but got expected error: {}", |  | ||||||
|                 e |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[cfg(target_os = "linux")] |  | ||||||
| #[test] |  | ||||||
| fn test_linux_service_manager() { |  | ||||||
|     use sal_service_manager::SystemdServiceManager; |  | ||||||
|  |  | ||||||
|     // Test creating Linux-specific service manager |  | ||||||
|     let manager = SystemdServiceManager::new(); |  | ||||||
|  |  | ||||||
|     // Test basic functionality |  | ||||||
|     let list_result = manager.list(); |  | ||||||
|     match list_result { |  | ||||||
|         Ok(services) => { |  | ||||||
|             println!( |  | ||||||
|                 "✓ Linux SystemdServiceManager created successfully, found {} services", |  | ||||||
|                 services.len() |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             println!( |  | ||||||
|                 "✓ Linux SystemdServiceManager created, but got expected error: {}", |  | ||||||
|                 e |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_service_status_debug() { |  | ||||||
|     use sal_service_manager::ServiceStatus; |  | ||||||
|  |  | ||||||
|     // Test that ServiceStatus can be debugged and cloned |  | ||||||
|     let statuses = vec![ |  | ||||||
|         ServiceStatus::Running, |  | ||||||
|         ServiceStatus::Stopped, |  | ||||||
|         ServiceStatus::Failed, |  | ||||||
|         ServiceStatus::Unknown, |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     for status in &statuses { |  | ||||||
|         let cloned = status.clone(); |  | ||||||
|         let debug_str = format!("{:?}", status); |  | ||||||
|  |  | ||||||
|         assert!(!debug_str.is_empty()); |  | ||||||
|         assert_eq!(status, &cloned); |  | ||||||
|  |  | ||||||
|         println!( |  | ||||||
|             "✓ ServiceStatus::{:?} debug and clone work correctly", |  | ||||||
|             status |  | ||||||
|         ); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_service_manager_error_debug() { |  | ||||||
|     use sal_service_manager::ServiceManagerError; |  | ||||||
|  |  | ||||||
|     // Test that ServiceManagerError can be debugged and displayed |  | ||||||
|     let errors = vec![ |  | ||||||
|         ServiceManagerError::ServiceNotFound("test".to_string()), |  | ||||||
|         ServiceManagerError::ServiceAlreadyExists("test".to_string()), |  | ||||||
|         ServiceManagerError::StartFailed("test".to_string(), "reason".to_string()), |  | ||||||
|         ServiceManagerError::StopFailed("test".to_string(), "reason".to_string()), |  | ||||||
|         ServiceManagerError::RestartFailed("test".to_string(), "reason".to_string()), |  | ||||||
|         ServiceManagerError::LogsFailed("test".to_string(), "reason".to_string()), |  | ||||||
|         ServiceManagerError::Other("generic error".to_string()), |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     for error in &errors { |  | ||||||
|         let debug_str = format!("{:?}", error); |  | ||||||
|         let display_str = format!("{}", error); |  | ||||||
|  |  | ||||||
|         assert!(!debug_str.is_empty()); |  | ||||||
|         assert!(!display_str.is_empty()); |  | ||||||
|  |  | ||||||
|         println!("✓ Error debug: {:?}", error); |  | ||||||
|         println!("✓ Error display: {}", error); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_service_manager_trait_object() { |  | ||||||
|     // Test that we can use ServiceManager as a trait object |  | ||||||
|     let manager: Box<dyn ServiceManager> = |  | ||||||
|         create_service_manager().expect("Failed to create service manager"); |  | ||||||
|  |  | ||||||
|     // Test that we can call methods through the trait object |  | ||||||
|     let list_result = manager.list(); |  | ||||||
|  |  | ||||||
|     match list_result { |  | ||||||
|         Ok(services) => { |  | ||||||
|             println!("✓ Trait object works, found {} services", services.len()); |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             println!("✓ Trait object works, got expected error: {}", e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Test exists method |  | ||||||
|     let exists_result = manager.exists("non-existent-service"); |  | ||||||
|     match exists_result { |  | ||||||
|         Ok(false) => println!("✓ Trait object exists method works correctly"), |  | ||||||
|         Ok(true) => println!("⚠ Unexpectedly found non-existent service"), |  | ||||||
|         Err(_) => println!("✓ Trait object exists method works (with error)"), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,177 +0,0 @@ | |||||||
| // Service lifecycle management test script |  | ||||||
| // This script tests REAL complete service lifecycle scenarios |  | ||||||
|  |  | ||||||
| print("=== Service Lifecycle Management Test ==="); |  | ||||||
|  |  | ||||||
| // Create service manager |  | ||||||
| let manager = create_service_manager(); |  | ||||||
| print("✓ Service manager created"); |  | ||||||
|  |  | ||||||
| // Test configuration - real services for testing |  | ||||||
| let test_services = [ |  | ||||||
|     #{ |  | ||||||
|         name: "lifecycle-test-1", |  | ||||||
|         binary_path: "/bin/echo", |  | ||||||
|         args: ["Lifecycle test 1"], |  | ||||||
|         working_directory: "/tmp", |  | ||||||
|         environment: #{}, |  | ||||||
|         auto_restart: false |  | ||||||
|     }, |  | ||||||
|     #{ |  | ||||||
|         name: "lifecycle-test-2", |  | ||||||
|         binary_path: "/bin/echo", |  | ||||||
|         args: ["Lifecycle test 2"], |  | ||||||
|         working_directory: "/tmp", |  | ||||||
|         environment: #{ "TEST_VAR": "test_value" }, |  | ||||||
|         auto_restart: false |  | ||||||
|     } |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| let total_tests = 0; |  | ||||||
| let passed_tests = 0; |  | ||||||
|  |  | ||||||
| // Test 1: Service Creation and Start |  | ||||||
| print("\n1. Testing service creation and start..."); |  | ||||||
| for service_config in test_services { |  | ||||||
|     print(`\nStarting service: ${service_config.name}`); |  | ||||||
|     try { |  | ||||||
|         start(manager, service_config); |  | ||||||
|         print(`  ✓ Service ${service_config.name} started successfully`); |  | ||||||
|         passed_tests += 1; |  | ||||||
|     } catch(e) { |  | ||||||
|         print(`  ✗ Service ${service_config.name} start failed: ${e}`); |  | ||||||
|     } |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 2: Service Existence Check |  | ||||||
| print("\n2. Testing service existence checks..."); |  | ||||||
| for service_config in test_services { |  | ||||||
|     print(`\nChecking existence of: ${service_config.name}`); |  | ||||||
|     try { |  | ||||||
|         let service_exists = exists(manager, service_config.name); |  | ||||||
|         if service_exists { |  | ||||||
|             print(`  ✓ Service ${service_config.name} exists: ${service_exists}`); |  | ||||||
|             passed_tests += 1; |  | ||||||
|         } else { |  | ||||||
|             print(`  ✗ Service ${service_config.name} doesn't exist after start`); |  | ||||||
|         } |  | ||||||
|     } catch(e) { |  | ||||||
|         print(`  ✗ Existence check failed for ${service_config.name}: ${e}`); |  | ||||||
|     } |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 3: Status Check |  | ||||||
| print("\n3. Testing status checks..."); |  | ||||||
| for service_config in test_services { |  | ||||||
|     print(`\nChecking status of: ${service_config.name}`); |  | ||||||
|     try { |  | ||||||
|         let service_status = status(manager, service_config.name); |  | ||||||
|         print(`  ✓ Service ${service_config.name} status: ${service_status}`); |  | ||||||
|         passed_tests += 1; |  | ||||||
|     } catch(e) { |  | ||||||
|         print(`  ✗ Status check failed for ${service_config.name}: ${e}`); |  | ||||||
|     } |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 4: Service List Check |  | ||||||
| print("\n4. Testing service list..."); |  | ||||||
| try { |  | ||||||
|     let services = list(manager); |  | ||||||
|     print(`  ✓ Service list retrieved (${services.len()} services)`); |  | ||||||
|  |  | ||||||
|     // Check if our test services are in the list |  | ||||||
|     for service_config in test_services { |  | ||||||
|         let found = false; |  | ||||||
|         for service in services { |  | ||||||
|             if service.contains(service_config.name) { |  | ||||||
|                 found = true; |  | ||||||
|                 print(`    ✓ Found ${service_config.name} in list`); |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if !found { |  | ||||||
|             print(`    ⚠ ${service_config.name} not found in service list`); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     passed_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`  ✗ Service list failed: ${e}`); |  | ||||||
| } |  | ||||||
| total_tests += 1; |  | ||||||
|  |  | ||||||
| // Test 5: Service Stop |  | ||||||
| print("\n5. Testing service stop..."); |  | ||||||
| for service_config in test_services { |  | ||||||
|     print(`\nStopping service: ${service_config.name}`); |  | ||||||
|     try { |  | ||||||
|         stop(manager, service_config.name); |  | ||||||
|         print(`  ✓ Service ${service_config.name} stopped successfully`); |  | ||||||
|         passed_tests += 1; |  | ||||||
|     } catch(e) { |  | ||||||
|         print(`  ✗ Service ${service_config.name} stop failed: ${e}`); |  | ||||||
|     } |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 6: Service Removal |  | ||||||
| print("\n6. Testing service removal..."); |  | ||||||
| for service_config in test_services { |  | ||||||
|     print(`\nRemoving service: ${service_config.name}`); |  | ||||||
|     try { |  | ||||||
|         remove(manager, service_config.name); |  | ||||||
|         print(`  ✓ Service ${service_config.name} removed successfully`); |  | ||||||
|         passed_tests += 1; |  | ||||||
|     } catch(e) { |  | ||||||
|         print(`  ✗ Service ${service_config.name} removal failed: ${e}`); |  | ||||||
|     } |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 7: Cleanup Verification |  | ||||||
| print("\n7. Testing cleanup verification..."); |  | ||||||
| for service_config in test_services { |  | ||||||
|     print(`\nVerifying removal of: ${service_config.name}`); |  | ||||||
|     try { |  | ||||||
|         let exists_after_remove = exists(manager, service_config.name); |  | ||||||
|         if !exists_after_remove { |  | ||||||
|             print(`  ✓ Service ${service_config.name} correctly doesn't exist after removal`); |  | ||||||
|             passed_tests += 1; |  | ||||||
|         } else { |  | ||||||
|             print(`  ✗ Service ${service_config.name} still exists after removal`); |  | ||||||
|         } |  | ||||||
|     } catch(e) { |  | ||||||
|         print(`  ✗ Cleanup verification failed for ${service_config.name}: ${e}`); |  | ||||||
|     } |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test Summary |  | ||||||
| print("\n=== Lifecycle Test Summary ==="); |  | ||||||
| print(`Services tested: ${test_services.len()}`); |  | ||||||
| print(`Total operations: ${total_tests}`); |  | ||||||
| print(`Successful operations: ${passed_tests}`); |  | ||||||
| print(`Failed operations: ${total_tests - passed_tests}`); |  | ||||||
| print(`Success rate: ${(passed_tests * 100) / total_tests}%`); |  | ||||||
|  |  | ||||||
| if passed_tests == total_tests { |  | ||||||
|     print("\n🎉 All lifecycle tests passed!"); |  | ||||||
|     print("Service manager is working correctly across all scenarios."); |  | ||||||
| } else { |  | ||||||
|     print(`\n⚠ ${total_tests - passed_tests} test(s) failed`); |  | ||||||
|     print("Some service manager operations need attention."); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| print("\n=== Service Lifecycle Test Complete ==="); |  | ||||||
|  |  | ||||||
| // Return test results |  | ||||||
| #{ |  | ||||||
|     summary: #{ |  | ||||||
|         total_tests: total_tests, |  | ||||||
|         passed_tests: passed_tests, |  | ||||||
|         success_rate: (passed_tests * 100) / total_tests, |  | ||||||
|         services_tested: test_services.len() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,218 +0,0 @@ | |||||||
| // Basic service manager functionality test script |  | ||||||
| // This script tests the REAL service manager through Rhai integration |  | ||||||
|  |  | ||||||
| print("=== Service Manager Basic Functionality Test ==="); |  | ||||||
|  |  | ||||||
| // Test configuration |  | ||||||
| let test_service_name = "rhai-test-service"; |  | ||||||
| let test_binary = "/bin/echo"; |  | ||||||
| let test_args = ["Hello from Rhai service manager test"]; |  | ||||||
|  |  | ||||||
| print(`Testing service: ${test_service_name}`); |  | ||||||
| print(`Binary: ${test_binary}`); |  | ||||||
| print(`Args: ${test_args}`); |  | ||||||
|  |  | ||||||
| // Test results tracking |  | ||||||
| let test_results = #{ |  | ||||||
|     creation: "NOT_RUN", |  | ||||||
|     exists_before: "NOT_RUN", |  | ||||||
|     start: "NOT_RUN", |  | ||||||
|     exists_after: "NOT_RUN", |  | ||||||
|     status: "NOT_RUN", |  | ||||||
|     list: "NOT_RUN", |  | ||||||
|     stop: "NOT_RUN", |  | ||||||
|     remove: "NOT_RUN", |  | ||||||
|     cleanup: "NOT_RUN" |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| let passed_tests = 0; |  | ||||||
| let total_tests = 0; |  | ||||||
|  |  | ||||||
| // Test 1: Service Manager Creation |  | ||||||
| print("\n1. Testing service manager creation..."); |  | ||||||
| try { |  | ||||||
|     let manager = create_service_manager(); |  | ||||||
|     print("✓ Service manager created successfully"); |  | ||||||
|     test_results["creation"] = "PASS"; |  | ||||||
|     passed_tests += 1; |  | ||||||
|     total_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`✗ Service manager creation failed: ${e}`); |  | ||||||
|     test_results["creation"] = "FAIL"; |  | ||||||
|     total_tests += 1; |  | ||||||
|     // Return early if we can't create the manager |  | ||||||
|     return test_results; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Create the service manager for all subsequent tests |  | ||||||
| let manager = create_service_manager(); |  | ||||||
|  |  | ||||||
| // Test 2: Check if service exists before creation |  | ||||||
| print("\n2. Testing service existence check (before creation)..."); |  | ||||||
| try { |  | ||||||
|     let exists_before = exists(manager, test_service_name); |  | ||||||
|     print(`✓ Service existence check: ${exists_before}`); |  | ||||||
|  |  | ||||||
|     if !exists_before { |  | ||||||
|         print("✓ Service correctly doesn't exist before creation"); |  | ||||||
|         test_results["exists_before"] = "PASS"; |  | ||||||
|         passed_tests += 1; |  | ||||||
|     } else { |  | ||||||
|         print("⚠ Service unexpectedly exists before creation"); |  | ||||||
|         test_results["exists_before"] = "WARN"; |  | ||||||
|     } |  | ||||||
|     total_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`✗ Service existence check failed: ${e}`); |  | ||||||
|     test_results["exists_before"] = "FAIL"; |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 3: Start the service |  | ||||||
| print("\n3. Testing service start..."); |  | ||||||
| try { |  | ||||||
|     // Create a service configuration object |  | ||||||
|     let service_config = #{ |  | ||||||
|         name: test_service_name, |  | ||||||
|         binary_path: test_binary, |  | ||||||
|         args: test_args, |  | ||||||
|         working_directory: "/tmp", |  | ||||||
|         environment: #{}, |  | ||||||
|         auto_restart: false |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     start(manager, service_config); |  | ||||||
|     print("✓ Service started successfully"); |  | ||||||
|     test_results["start"] = "PASS"; |  | ||||||
|     passed_tests += 1; |  | ||||||
|     total_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`✗ Service start failed: ${e}`); |  | ||||||
|     test_results["start"] = "FAIL"; |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 4: Check if service exists after creation |  | ||||||
| print("\n4. Testing service existence check (after creation)..."); |  | ||||||
| try { |  | ||||||
|     let exists_after = exists(manager, test_service_name); |  | ||||||
|     print(`✓ Service existence check: ${exists_after}`); |  | ||||||
|  |  | ||||||
|     if exists_after { |  | ||||||
|         print("✓ Service correctly exists after creation"); |  | ||||||
|         test_results["exists_after"] = "PASS"; |  | ||||||
|         passed_tests += 1; |  | ||||||
|     } else { |  | ||||||
|         print("✗ Service doesn't exist after creation"); |  | ||||||
|         test_results["exists_after"] = "FAIL"; |  | ||||||
|     } |  | ||||||
|     total_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`✗ Service existence check failed: ${e}`); |  | ||||||
|     test_results["exists_after"] = "FAIL"; |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 5: Check service status |  | ||||||
| print("\n5. Testing service status..."); |  | ||||||
| try { |  | ||||||
|     let service_status = status(manager, test_service_name); |  | ||||||
|     print(`✓ Service status: ${service_status}`); |  | ||||||
|     test_results["status"] = "PASS"; |  | ||||||
|     passed_tests += 1; |  | ||||||
|     total_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`✗ Service status check failed: ${e}`); |  | ||||||
|     test_results["status"] = "FAIL"; |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 6: List services |  | ||||||
| print("\n6. Testing service list..."); |  | ||||||
| try { |  | ||||||
|     let services = list(manager); |  | ||||||
|     print("✓ Service list retrieved"); |  | ||||||
|  |  | ||||||
|     // Skip service search due to Rhai type constraints with Vec iteration |  | ||||||
|     print("  ⚠️  Skipping service search due to Rhai type constraints"); |  | ||||||
|  |  | ||||||
|     test_results["list"] = "PASS"; |  | ||||||
|     passed_tests += 1; |  | ||||||
|     total_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`✗ Service list failed: ${e}`); |  | ||||||
|     test_results["list"] = "FAIL"; |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 7: Stop the service |  | ||||||
| print("\n7. Testing service stop..."); |  | ||||||
| try { |  | ||||||
|     stop(manager, test_service_name); |  | ||||||
|     print(`✓ Service stopped: ${test_service_name}`); |  | ||||||
|     test_results["stop"] = "PASS"; |  | ||||||
|     passed_tests += 1; |  | ||||||
|     total_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`✗ Service stop failed: ${e}`); |  | ||||||
|     test_results["stop"] = "FAIL"; |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 8: Remove the service |  | ||||||
| print("\n8. Testing service remove..."); |  | ||||||
| try { |  | ||||||
|     remove(manager, test_service_name); |  | ||||||
|     print(`✓ Service removed: ${test_service_name}`); |  | ||||||
|     test_results["remove"] = "PASS"; |  | ||||||
|     passed_tests += 1; |  | ||||||
|     total_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`✗ Service remove failed: ${e}`); |  | ||||||
|     test_results["remove"] = "FAIL"; |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test 9: Verify cleanup |  | ||||||
| print("\n9. Testing cleanup verification..."); |  | ||||||
| try { |  | ||||||
|     let exists_after_remove = exists(manager, test_service_name); |  | ||||||
|     if !exists_after_remove { |  | ||||||
|         print("✓ Service correctly doesn't exist after removal"); |  | ||||||
|         test_results["cleanup"] = "PASS"; |  | ||||||
|         passed_tests += 1; |  | ||||||
|     } else { |  | ||||||
|         print("✗ Service still exists after removal"); |  | ||||||
|         test_results["cleanup"] = "FAIL"; |  | ||||||
|     } |  | ||||||
|     total_tests += 1; |  | ||||||
| } catch(e) { |  | ||||||
|     print(`✗ Cleanup verification failed: ${e}`); |  | ||||||
|     test_results["cleanup"] = "FAIL"; |  | ||||||
|     total_tests += 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Test Summary |  | ||||||
| print("\n=== Test Summary ==="); |  | ||||||
| print(`Total tests: ${total_tests}`); |  | ||||||
| print(`Passed: ${passed_tests}`); |  | ||||||
| print(`Failed: ${total_tests - passed_tests}`); |  | ||||||
| print(`Success rate: ${(passed_tests * 100) / total_tests}%`); |  | ||||||
|  |  | ||||||
| print("\nDetailed Results:"); |  | ||||||
| for test_name in test_results.keys() { |  | ||||||
|     let result = test_results[test_name]; |  | ||||||
|     let status_icon = if result == "PASS" { "✓" } else if result == "FAIL" { "✗" } else { "⚠" }; |  | ||||||
|     print(`  ${status_icon} ${test_name}: ${result}`); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| if passed_tests == total_tests { |  | ||||||
|     print("\n🎉 All tests passed!"); |  | ||||||
| } else { |  | ||||||
|     print(`\n⚠ ${total_tests - passed_tests} test(s) failed`); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| print("\n=== Service Manager Basic Test Complete ==="); |  | ||||||
|  |  | ||||||
| // Return test results for potential use by calling code |  | ||||||
| test_results |  | ||||||
| @@ -1,252 +0,0 @@ | |||||||
| use rhai::{Engine, EvalAltResult}; |  | ||||||
| use std::fs; |  | ||||||
| use std::path::Path; |  | ||||||
|  |  | ||||||
| /// Helper function to create a Rhai engine for service manager testing |  | ||||||
| fn create_service_manager_engine() -> Result<Engine, Box<EvalAltResult>> { |  | ||||||
|     #[cfg(feature = "rhai")] |  | ||||||
|     { |  | ||||||
|         let mut engine = Engine::new(); |  | ||||||
|         // Register the service manager module for real testing |  | ||||||
|         sal_service_manager::rhai::register_service_manager_module(&mut engine)?; |  | ||||||
|         Ok(engine) |  | ||||||
|     } |  | ||||||
|     #[cfg(not(feature = "rhai"))] |  | ||||||
|     { |  | ||||||
|         Ok(Engine::new()) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Helper function to run a Rhai script file |  | ||||||
| fn run_rhai_script(script_path: &str) -> Result<rhai::Dynamic, Box<EvalAltResult>> { |  | ||||||
|     let engine = create_service_manager_engine()?; |  | ||||||
|  |  | ||||||
|     // Read the script file |  | ||||||
|     let script_content = fs::read_to_string(script_path) |  | ||||||
|         .map_err(|e| format!("Failed to read script file {}: {}", script_path, e))?; |  | ||||||
|  |  | ||||||
|     // Execute the script |  | ||||||
|     engine.eval::<rhai::Dynamic>(&script_content) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_rhai_service_manager_basic() { |  | ||||||
|     let script_path = "tests/rhai/service_manager_basic.rhai"; |  | ||||||
|  |  | ||||||
|     if !Path::new(script_path).exists() { |  | ||||||
|         println!("⚠ Skipping test: Rhai script not found at {}", script_path); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     println!("Running Rhai service manager basic test..."); |  | ||||||
|  |  | ||||||
|     match run_rhai_script(script_path) { |  | ||||||
|         Ok(result) => { |  | ||||||
|             println!("✓ Rhai basic test completed successfully"); |  | ||||||
|  |  | ||||||
|             // Try to extract test results if the script returns them |  | ||||||
|             if let Some(map) = result.try_cast::<rhai::Map>() { |  | ||||||
|                 println!("Test results received from Rhai script:"); |  | ||||||
|                 for (key, value) in map.iter() { |  | ||||||
|                     println!("  {}: {:?}", key, value); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Check if all tests passed |  | ||||||
|                 let all_passed = map.values().all(|v| { |  | ||||||
|                     if let Some(s) = v.clone().try_cast::<String>() { |  | ||||||
|                         s == "PASS" |  | ||||||
|                     } else { |  | ||||||
|                         false |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 if all_passed { |  | ||||||
|                     println!("✓ All Rhai tests reported as PASS"); |  | ||||||
|                 } else { |  | ||||||
|                     println!("⚠ Some Rhai tests did not pass"); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             println!("✗ Rhai basic test failed: {}", e); |  | ||||||
|             assert!(false, "Rhai script execution failed: {}", e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_rhai_service_lifecycle() { |  | ||||||
|     let script_path = "tests/rhai/service_lifecycle.rhai"; |  | ||||||
|  |  | ||||||
|     if !Path::new(script_path).exists() { |  | ||||||
|         println!("⚠ Skipping test: Rhai script not found at {}", script_path); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     println!("Running Rhai service lifecycle test..."); |  | ||||||
|  |  | ||||||
|     match run_rhai_script(script_path) { |  | ||||||
|         Ok(result) => { |  | ||||||
|             println!("✓ Rhai lifecycle test completed successfully"); |  | ||||||
|  |  | ||||||
|             // Try to extract test results if the script returns them |  | ||||||
|             if let Some(map) = result.try_cast::<rhai::Map>() { |  | ||||||
|                 println!("Lifecycle test results received from Rhai script:"); |  | ||||||
|  |  | ||||||
|                 // Extract summary if available |  | ||||||
|                 if let Some(summary) = map.get("summary") { |  | ||||||
|                     if let Some(summary_map) = summary.clone().try_cast::<rhai::Map>() { |  | ||||||
|                         println!("Summary:"); |  | ||||||
|                         for (key, value) in summary_map.iter() { |  | ||||||
|                             println!("  {}: {:?}", key, value); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Extract performance metrics if available |  | ||||||
|                 if let Some(performance) = map.get("performance") { |  | ||||||
|                     if let Some(perf_map) = performance.clone().try_cast::<rhai::Map>() { |  | ||||||
|                         println!("Performance:"); |  | ||||||
|                         for (key, value) in perf_map.iter() { |  | ||||||
|                             println!("  {}: {:?}", key, value); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             println!("✗ Rhai lifecycle test failed: {}", e); |  | ||||||
|             assert!(false, "Rhai script execution failed: {}", e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_rhai_engine_functionality() { |  | ||||||
|     println!("Testing basic Rhai engine functionality..."); |  | ||||||
|  |  | ||||||
|     let engine = create_service_manager_engine().expect("Failed to create Rhai engine"); |  | ||||||
|  |  | ||||||
|     // Test basic Rhai functionality |  | ||||||
|     let test_script = r#" |  | ||||||
|         let test_results = #{ |  | ||||||
|             basic_math: 2 + 2 == 4, |  | ||||||
|             string_ops: "hello".len() == 5, |  | ||||||
|             array_ops: [1, 2, 3].len() == 3, |  | ||||||
|             map_ops: #{ a: 1, b: 2 }.len() == 2 |  | ||||||
|         }; |  | ||||||
|          |  | ||||||
|         let all_passed = true; |  | ||||||
|         for result in test_results.values() { |  | ||||||
|             if !result { |  | ||||||
|                 all_passed = false; |  | ||||||
|                 break; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         #{ |  | ||||||
|             results: test_results, |  | ||||||
|             all_passed: all_passed |  | ||||||
|         } |  | ||||||
|     "#; |  | ||||||
|  |  | ||||||
|     match engine.eval::<rhai::Dynamic>(test_script) { |  | ||||||
|         Ok(result) => { |  | ||||||
|             if let Some(map) = result.try_cast::<rhai::Map>() { |  | ||||||
|                 if let Some(all_passed) = map.get("all_passed") { |  | ||||||
|                     if let Some(passed) = all_passed.clone().try_cast::<bool>() { |  | ||||||
|                         if passed { |  | ||||||
|                             println!("✓ All basic Rhai functionality tests passed"); |  | ||||||
|                         } else { |  | ||||||
|                             println!("✗ Some basic Rhai functionality tests failed"); |  | ||||||
|                             assert!(false, "Basic Rhai tests failed"); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if let Some(results) = map.get("results") { |  | ||||||
|                     if let Some(results_map) = results.clone().try_cast::<rhai::Map>() { |  | ||||||
|                         println!("Detailed results:"); |  | ||||||
|                         for (test_name, result) in results_map.iter() { |  | ||||||
|                             let status = if let Some(passed) = result.clone().try_cast::<bool>() { |  | ||||||
|                                 if passed { |  | ||||||
|                                     "✓" |  | ||||||
|                                 } else { |  | ||||||
|                                     "✗" |  | ||||||
|                                 } |  | ||||||
|                             } else { |  | ||||||
|                                 "?" |  | ||||||
|                             }; |  | ||||||
|                             println!("  {} {}: {:?}", status, test_name, result); |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             println!("✗ Basic Rhai functionality test failed: {}", e); |  | ||||||
|             assert!(false, "Basic Rhai test failed: {}", e); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_rhai_script_error_handling() { |  | ||||||
|     println!("Testing Rhai error handling..."); |  | ||||||
|  |  | ||||||
|     let engine = create_service_manager_engine().expect("Failed to create Rhai engine"); |  | ||||||
|  |  | ||||||
|     // Test script with intentional error |  | ||||||
|     let error_script = r#" |  | ||||||
|         let result = "test"; |  | ||||||
|         result.non_existent_method(); // This should cause an error |  | ||||||
|     "#; |  | ||||||
|  |  | ||||||
|     match engine.eval::<rhai::Dynamic>(error_script) { |  | ||||||
|         Ok(_) => { |  | ||||||
|             println!("⚠ Expected error but script succeeded"); |  | ||||||
|             assert!( |  | ||||||
|                 false, |  | ||||||
|                 "Error handling test failed - expected error but got success" |  | ||||||
|             ); |  | ||||||
|         } |  | ||||||
|         Err(e) => { |  | ||||||
|             println!("✓ Error correctly caught: {}", e); |  | ||||||
|             // Verify it's the expected type of error |  | ||||||
|             assert!(e.to_string().contains("method") || e.to_string().contains("function")); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[test] |  | ||||||
| fn test_rhai_script_files_exist() { |  | ||||||
|     println!("Checking that Rhai test scripts exist..."); |  | ||||||
|  |  | ||||||
|     let script_files = [ |  | ||||||
|         "tests/rhai/service_manager_basic.rhai", |  | ||||||
|         "tests/rhai/service_lifecycle.rhai", |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     for script_file in &script_files { |  | ||||||
|         if Path::new(script_file).exists() { |  | ||||||
|             println!("✓ Found script: {}", script_file); |  | ||||||
|  |  | ||||||
|             // Verify the file is readable and not empty |  | ||||||
|             match fs::read_to_string(script_file) { |  | ||||||
|                 Ok(content) => { |  | ||||||
|                     if content.trim().is_empty() { |  | ||||||
|                         assert!(false, "Script file {} is empty", script_file); |  | ||||||
|                     } |  | ||||||
|                     println!("  Content length: {} characters", content.len()); |  | ||||||
|                 } |  | ||||||
|                 Err(e) => { |  | ||||||
|                     assert!(false, "Failed to read script file {}: {}", script_file, e); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             assert!(false, "Required script file not found: {}", script_file); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     println!("✓ All required Rhai script files exist and are readable"); |  | ||||||
| } |  | ||||||
| @@ -1,317 +0,0 @@ | |||||||
| use sal_service_manager::{ |  | ||||||
|     ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus, ZinitServiceManager, |  | ||||||
| }; |  | ||||||
| use std::collections::HashMap; |  | ||||||
| use std::time::Duration; |  | ||||||
| use tokio::time::sleep; |  | ||||||
|  |  | ||||||
| /// Helper function to find an available Zinit socket path |  | ||||||
| async fn get_available_socket_path() -> Option<String> { |  | ||||||
|     let socket_paths = [ |  | ||||||
|         "/var/run/zinit.sock", |  | ||||||
|         "/tmp/zinit.sock", |  | ||||||
|         "/run/zinit.sock", |  | ||||||
|         "./zinit.sock", |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     for path in &socket_paths { |  | ||||||
|         // Try to create a ZinitServiceManager to test connectivity |  | ||||||
|         if let Ok(manager) = ZinitServiceManager::new(path) { |  | ||||||
|             // Test if we can list services (basic connectivity test) |  | ||||||
|             if manager.list().is_ok() { |  | ||||||
|                 println!("✓ Found working Zinit socket at: {}", path); |  | ||||||
|                 return Some(path.to_string()); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     None |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /// Helper function to clean up test services |  | ||||||
| async fn cleanup_test_service(manager: &dyn ServiceManager, service_name: &str) { |  | ||||||
|     let _ = manager.stop(service_name); |  | ||||||
|     let _ = manager.remove(service_name); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[tokio::test] |  | ||||||
| async fn test_zinit_service_manager_creation() { |  | ||||||
|     if let Some(socket_path) = get_available_socket_path().await { |  | ||||||
|         let manager = ZinitServiceManager::new(&socket_path); |  | ||||||
|         assert!( |  | ||||||
|             manager.is_ok(), |  | ||||||
|             "Should be able to create ZinitServiceManager" |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         let manager = manager.unwrap(); |  | ||||||
|  |  | ||||||
|         // Test basic connectivity by listing services |  | ||||||
|         let list_result = manager.list(); |  | ||||||
|         assert!(list_result.is_ok(), "Should be able to list services"); |  | ||||||
|  |  | ||||||
|         println!("✓ ZinitServiceManager created successfully"); |  | ||||||
|     } else { |  | ||||||
|         println!("⚠ Skipping test_zinit_service_manager_creation: No Zinit socket available"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[tokio::test] |  | ||||||
| async fn test_service_lifecycle() { |  | ||||||
|     if let Some(socket_path) = get_available_socket_path().await { |  | ||||||
|         let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager"); |  | ||||||
|         let service_name = "test-lifecycle-service"; |  | ||||||
|  |  | ||||||
|         // Clean up any existing service first |  | ||||||
|         cleanup_test_service(&manager, service_name).await; |  | ||||||
|  |  | ||||||
|         let config = ServiceConfig { |  | ||||||
|             name: service_name.to_string(), |  | ||||||
|             binary_path: "echo".to_string(), |  | ||||||
|             args: vec!["Hello from lifecycle test".to_string()], |  | ||||||
|             working_directory: Some("/tmp".to_string()), |  | ||||||
|             environment: HashMap::new(), |  | ||||||
|             auto_restart: false, |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         // Test service creation and start |  | ||||||
|         println!("Testing service creation and start..."); |  | ||||||
|         let start_result = manager.start(&config); |  | ||||||
|         match start_result { |  | ||||||
|             Ok(_) => { |  | ||||||
|                 println!("✓ Service started successfully"); |  | ||||||
|  |  | ||||||
|                 // Wait a bit for the service to run |  | ||||||
|                 sleep(Duration::from_millis(500)).await; |  | ||||||
|  |  | ||||||
|                 // Test service exists |  | ||||||
|                 let exists = manager.exists(service_name); |  | ||||||
|                 assert!(exists.is_ok(), "Should be able to check if service exists"); |  | ||||||
|  |  | ||||||
|                 if let Ok(true) = exists { |  | ||||||
|                     println!("✓ Service exists check passed"); |  | ||||||
|  |  | ||||||
|                     // Test service status |  | ||||||
|                     let status_result = manager.status(service_name); |  | ||||||
|                     match status_result { |  | ||||||
|                         Ok(status) => { |  | ||||||
|                             println!("✓ Service status: {:?}", status); |  | ||||||
|                             assert!( |  | ||||||
|                                 matches!(status, ServiceStatus::Running | ServiceStatus::Stopped), |  | ||||||
|                                 "Service should be running or stopped (for oneshot)" |  | ||||||
|                             ); |  | ||||||
|                         } |  | ||||||
|                         Err(e) => println!("⚠ Status check failed: {}", e), |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     // Test service logs |  | ||||||
|                     let logs_result = manager.logs(service_name, None); |  | ||||||
|                     match logs_result { |  | ||||||
|                         Ok(logs) => { |  | ||||||
|                             println!("✓ Retrieved logs: {}", logs.len()); |  | ||||||
|                             // For echo command, we should have some output |  | ||||||
|                             assert!( |  | ||||||
|                                 !logs.is_empty() || logs.contains("Hello"), |  | ||||||
|                                 "Should have log output" |  | ||||||
|                             ); |  | ||||||
|                         } |  | ||||||
|                         Err(e) => println!("⚠ Logs retrieval failed: {}", e), |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     // Test service list |  | ||||||
|                     let list_result = manager.list(); |  | ||||||
|                     match list_result { |  | ||||||
|                         Ok(services) => { |  | ||||||
|                             println!("✓ Listed {} services", services.len()); |  | ||||||
|                             assert!( |  | ||||||
|                                 services.contains(&service_name.to_string()), |  | ||||||
|                                 "Service should appear in list" |  | ||||||
|                             ); |  | ||||||
|                         } |  | ||||||
|                         Err(e) => println!("⚠ List services failed: {}", e), |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Test service stop |  | ||||||
|                 println!("Testing service stop..."); |  | ||||||
|                 let stop_result = manager.stop(service_name); |  | ||||||
|                 match stop_result { |  | ||||||
|                     Ok(_) => println!("✓ Service stopped successfully"), |  | ||||||
|                     Err(e) => println!("⚠ Stop failed: {}", e), |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 // Test service removal |  | ||||||
|                 println!("Testing service removal..."); |  | ||||||
|                 let remove_result = manager.remove(service_name); |  | ||||||
|                 match remove_result { |  | ||||||
|                     Ok(_) => println!("✓ Service removed successfully"), |  | ||||||
|                     Err(e) => println!("⚠ Remove failed: {}", e), |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Err(e) => { |  | ||||||
|                 println!("⚠ Service creation/start failed: {}", e); |  | ||||||
|                 // This might be expected if zinit doesn't allow service creation |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Final cleanup |  | ||||||
|         cleanup_test_service(&manager, service_name).await; |  | ||||||
|     } else { |  | ||||||
|         println!("⚠ Skipping test_service_lifecycle: No Zinit socket available"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[tokio::test] |  | ||||||
| async fn test_service_start_and_confirm() { |  | ||||||
|     if let Some(socket_path) = get_available_socket_path().await { |  | ||||||
|         let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager"); |  | ||||||
|         let service_name = "test-start-confirm-service"; |  | ||||||
|  |  | ||||||
|         // Clean up any existing service first |  | ||||||
|         cleanup_test_service(&manager, service_name).await; |  | ||||||
|  |  | ||||||
|         let config = ServiceConfig { |  | ||||||
|             name: service_name.to_string(), |  | ||||||
|             binary_path: "sleep".to_string(), |  | ||||||
|             args: vec!["5".to_string()], // Sleep for 5 seconds |  | ||||||
|             working_directory: Some("/tmp".to_string()), |  | ||||||
|             environment: HashMap::new(), |  | ||||||
|             auto_restart: false, |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         // Test start_and_confirm with timeout |  | ||||||
|         println!("Testing start_and_confirm with timeout..."); |  | ||||||
|         let start_result = manager.start_and_confirm(&config, 10); |  | ||||||
|         match start_result { |  | ||||||
|             Ok(_) => { |  | ||||||
|                 println!("✓ Service started and confirmed successfully"); |  | ||||||
|  |  | ||||||
|                 // Verify it's actually running |  | ||||||
|                 let status = manager.status(service_name); |  | ||||||
|                 match status { |  | ||||||
|                     Ok(ServiceStatus::Running) => println!("✓ Service confirmed running"), |  | ||||||
|                     Ok(other_status) => { |  | ||||||
|                         println!("⚠ Service in unexpected state: {:?}", other_status) |  | ||||||
|                     } |  | ||||||
|                     Err(e) => println!("⚠ Status check failed: {}", e), |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Err(e) => { |  | ||||||
|                 println!("⚠ start_and_confirm failed: {}", e); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Test start_existing_and_confirm |  | ||||||
|         println!("Testing start_existing_and_confirm..."); |  | ||||||
|         let start_existing_result = manager.start_existing_and_confirm(service_name, 5); |  | ||||||
|         match start_existing_result { |  | ||||||
|             Ok(_) => println!("✓ start_existing_and_confirm succeeded"), |  | ||||||
|             Err(e) => println!("⚠ start_existing_and_confirm failed: {}", e), |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Cleanup |  | ||||||
|         cleanup_test_service(&manager, service_name).await; |  | ||||||
|     } else { |  | ||||||
|         println!("⚠ Skipping test_service_start_and_confirm: No Zinit socket available"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[tokio::test] |  | ||||||
| async fn test_service_restart() { |  | ||||||
|     if let Some(socket_path) = get_available_socket_path().await { |  | ||||||
|         let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager"); |  | ||||||
|         let service_name = "test-restart-service"; |  | ||||||
|  |  | ||||||
|         // Clean up any existing service first |  | ||||||
|         cleanup_test_service(&manager, service_name).await; |  | ||||||
|  |  | ||||||
|         let config = ServiceConfig { |  | ||||||
|             name: service_name.to_string(), |  | ||||||
|             binary_path: "echo".to_string(), |  | ||||||
|             args: vec!["Restart test".to_string()], |  | ||||||
|             working_directory: Some("/tmp".to_string()), |  | ||||||
|             environment: HashMap::new(), |  | ||||||
|             auto_restart: true, // Enable auto-restart for this test |  | ||||||
|         }; |  | ||||||
|  |  | ||||||
|         // Start the service first |  | ||||||
|         let start_result = manager.start(&config); |  | ||||||
|         if start_result.is_ok() { |  | ||||||
|             // Wait for service to be established |  | ||||||
|             sleep(Duration::from_millis(1000)).await; |  | ||||||
|  |  | ||||||
|             // Test restart |  | ||||||
|             println!("Testing service restart..."); |  | ||||||
|             let restart_result = manager.restart(service_name); |  | ||||||
|             match restart_result { |  | ||||||
|                 Ok(_) => { |  | ||||||
|                     println!("✓ Service restarted successfully"); |  | ||||||
|  |  | ||||||
|                     // Wait and check status |  | ||||||
|                     sleep(Duration::from_millis(500)).await; |  | ||||||
|  |  | ||||||
|                     let status_result = manager.status(service_name); |  | ||||||
|                     match status_result { |  | ||||||
|                         Ok(status) => { |  | ||||||
|                             println!("✓ Service state after restart: {:?}", status); |  | ||||||
|                         } |  | ||||||
|                         Err(e) => println!("⚠ Status check after restart failed: {}", e), |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 Err(e) => { |  | ||||||
|                     println!("⚠ Restart failed: {}", e); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Cleanup |  | ||||||
|         cleanup_test_service(&manager, service_name).await; |  | ||||||
|     } else { |  | ||||||
|         println!("⚠ Skipping test_service_restart: No Zinit socket available"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #[tokio::test] |  | ||||||
| async fn test_error_handling() { |  | ||||||
|     if let Some(socket_path) = get_available_socket_path().await { |  | ||||||
|         let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager"); |  | ||||||
|  |  | ||||||
|         // Test operations on non-existent service |  | ||||||
|         let non_existent_service = "non-existent-service-12345"; |  | ||||||
|  |  | ||||||
|         // Test status of non-existent service |  | ||||||
|         let status_result = manager.status(non_existent_service); |  | ||||||
|         match status_result { |  | ||||||
|             Err(ServiceManagerError::ServiceNotFound(_)) => { |  | ||||||
|                 println!("✓ Correctly returned ServiceNotFound for non-existent service"); |  | ||||||
|             } |  | ||||||
|             Err(other_error) => { |  | ||||||
|                 println!( |  | ||||||
|                     "⚠ Got different error for non-existent service: {}", |  | ||||||
|                     other_error |  | ||||||
|                 ); |  | ||||||
|             } |  | ||||||
|             Ok(_) => { |  | ||||||
|                 println!("⚠ Unexpectedly found non-existent service"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Test exists for non-existent service |  | ||||||
|         let exists_result = manager.exists(non_existent_service); |  | ||||||
|         match exists_result { |  | ||||||
|             Ok(false) => println!("✓ Correctly reported non-existent service as not existing"), |  | ||||||
|             Ok(true) => println!("⚠ Incorrectly reported non-existent service as existing"), |  | ||||||
|             Err(e) => println!("⚠ Error checking existence: {}", e), |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Test stop of non-existent service |  | ||||||
|         let stop_result = manager.stop(non_existent_service); |  | ||||||
|         match stop_result { |  | ||||||
|             Err(_) => println!("✓ Correctly failed to stop non-existent service"), |  | ||||||
|             Ok(_) => println!("⚠ Unexpectedly succeeded in stopping non-existent service"), |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         println!("✓ Error handling tests completed"); |  | ||||||
|     } else { |  | ||||||
|         println!("⚠ Skipping test_error_handling: No Zinit socket available"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										14
									
								
								config/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								config/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | # Environment Configuration | ||||||
|  |  | ||||||
|  | To set up your environment variables: | ||||||
|  |  | ||||||
|  | 1. Copy the template file to `env.sh`: | ||||||
|  |  | ||||||
|  |     ```bash | ||||||
|  |     cp config/myenv_templ.sh config/env.sh | ||||||
|  |     ``` | ||||||
|  |  | ||||||
|  | 2. Edit `config/env.sh` and fill in your specific values for the variables. | ||||||
|  |  | ||||||
|  | 3. This file (`config/env.sh`) is excluded from version control by the project's `.gitignore` configuration, ensuring your sensitive information remains local and is never committed to the repository. | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								config/myenv_templ.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								config/myenv_templ.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  |  | ||||||
|  |  | ||||||
|  | export OPENROUTER_API_KEY="" | ||||||
|  | export GROQ_API_KEY="" | ||||||
|  | export CEREBRAS_API_KEY="" | ||||||
|  | export OPENAI_API_KEY="sk-xxxxxxx" | ||||||
							
								
								
									
										43
									
								
								examples/rfsclient/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								examples/rfsclient/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | # RFS Client Rhai Examples | ||||||
|  |  | ||||||
|  | This folder contains Rhai examples that use the SAL RFS client wrappers registered by `sal::rhai::register(&mut engine)` and executed by the `herodo` binary. | ||||||
|  |  | ||||||
|  | ## Quick start | ||||||
|  |  | ||||||
|  | Run the auth + upload + download example (uses hardcoded credentials and `/etc/hosts` as input): | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | cargo run -p herodo -- examples/rfsclient/auth_and_upload.rhai | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | By default, the script: | ||||||
|  |  | ||||||
|  | - Uses base URL `http://127.0.0.1:8080` | ||||||
|  | - Uses credentials `user` / `password` | ||||||
|  | - Uploads the file `/etc/hosts` | ||||||
|  | - Downloads to `/tmp/rfs_example_out.txt` | ||||||
|  |  | ||||||
|  | To customize, edit `examples/rfsclient/auth_and_upload.rhai` near the top and change `BASE_URL`, `USER`, `PASS`, and file paths. | ||||||
|  |  | ||||||
|  | ## What the example does | ||||||
|  |  | ||||||
|  | - Creates the RFS client: `rfs_create_client(BASE_URL, USER, PASS, TIMEOUT)` | ||||||
|  | - Health check: `rfs_health_check()` | ||||||
|  | - Authenticates: `rfs_authenticate()` | ||||||
|  | - Uploads a file: `rfs_upload_file(local_path, chunk_size, verify)` → returns file hash | ||||||
|  | - Downloads it back: `rfs_download_file(file_id_or_hash, dest_path, verify)` → returns unit (throws on error) | ||||||
|  |  | ||||||
|  | See `examples/rfsclient/auth_and_upload.rhai` for details. | ||||||
|  |  | ||||||
|  | ## Using the Rust client directly (optional) | ||||||
|  |  | ||||||
|  | If you want to use the Rust API (without Rhai), depend on `sal-rfs-client` and see: | ||||||
|  |  | ||||||
|  | - `packages/clients/rfsclient/src/client.rs` (`RfsClient`) | ||||||
|  | - `packages/clients/rfsclient/src/types.rs` (config and option types) | ||||||
|  | - `packages/clients/rfsclient/examples/` (example usage) | ||||||
|  |  | ||||||
|  | ## Troubleshooting | ||||||
|  |  | ||||||
|  | - Auth failures: verify credentials and that the server requires/authenticates them. | ||||||
|  | - Connection errors: verify the base URL is reachable from your machine. | ||||||
							
								
								
									
										41
									
								
								examples/rfsclient/auth_and_upload.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								examples/rfsclient/auth_and_upload.rhai
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | |||||||
|  | // RFS Client: Auth + Upload + Download example | ||||||
|  | // Prereqs: | ||||||
|  | // - RFS server reachable at RFS_BASE_URL | ||||||
|  | // - Valid credentials in env: RFS_USER, RFS_PASS | ||||||
|  | // - Run with herodo so the SAL Rhai modules are registered | ||||||
|  |  | ||||||
|  | // NOTE: env_get not available in this runtime; hardcode or replace with your env loader | ||||||
|  | let BASE_URL = "http://127.0.0.1:8080"; | ||||||
|  | let USER = "user"; | ||||||
|  | let PASS = "password"; | ||||||
|  | let TIMEOUT = 30; // seconds | ||||||
|  |  | ||||||
|  | if BASE_URL == "" { throw "Set BASE_URL in the script"; } | ||||||
|  |  | ||||||
|  | // Create client | ||||||
|  | let ok = rfs_create_client(BASE_URL, USER, PASS, TIMEOUT); | ||||||
|  | if !ok { throw "Failed to create RFS client"; } | ||||||
|  |  | ||||||
|  | // Optional health check | ||||||
|  | let health = rfs_health_check(); | ||||||
|  | print(`RFS health: ${health}`); | ||||||
|  |  | ||||||
|  | // Authenticate (required for some operations) | ||||||
|  | let auth_ok = rfs_authenticate(); | ||||||
|  | if !auth_ok { throw "Authentication failed"; } | ||||||
|  |  | ||||||
|  | // Upload a local file | ||||||
|  | // Use an existing readable file to avoid needing os_write_file module | ||||||
|  | let local_file = "/etc/hosts"; | ||||||
|  | // rfs_upload_file(file_path, chunk_size, verify) | ||||||
|  | let hash = rfs_upload_file(local_file, 0, false); | ||||||
|  | print(`Uploaded file hash: ${hash}`); | ||||||
|  |  | ||||||
|  | // Download it back | ||||||
|  | let out_path = "/tmp/rfs_example_out.txt"; | ||||||
|  | // rfs_download_file(file_id, output_path, verify) returns unit and throws on error | ||||||
|  | rfs_download_file(hash, out_path, false); | ||||||
|  |  | ||||||
|  | print(`Downloaded to: ${out_path}`); | ||||||
|  |  | ||||||
|  | true | ||||||
							
								
								
									
										15
									
								
								examples_rust/ai/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								examples_rust/ai/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | [package] | ||||||
|  | name = "openrouter_example" | ||||||
|  | version = "0.1.0" | ||||||
|  | edition = "2021" | ||||||
|  |  | ||||||
|  | [workspace] | ||||||
|  |  | ||||||
|  | [[bin]] | ||||||
|  | name = "openrouter_example" | ||||||
|  | path = "openrouter_example.rs" | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | codemonkey = { path = "../../packages/ai/codemonkey" } | ||||||
|  | openai-api-rs = "6.0.8" | ||||||
|  | tokio = { version = "1.0", features = ["full"] } | ||||||
							
								
								
									
										47
									
								
								examples_rust/ai/openrouter_example.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								examples_rust/ai/openrouter_example.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | |||||||
|  | use codemonkey::{create_ai_provider, AIProviderType, CompletionRequestBuilder, Message, MessageRole, Content}; | ||||||
|  | use std::error::Error; | ||||||
|  |  | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() -> Result<(), Box<dyn Error>> { | ||||||
|  |  | ||||||
|  |     let (mut provider, provider_type) = create_ai_provider(AIProviderType::OpenRouter)?; | ||||||
|  |  | ||||||
|  |     let messages = vec![Message { | ||||||
|  |         role: MessageRole::user, | ||||||
|  |         content: Content::Text("Explain the concept of a factory design pattern in Rust.".to_string()), | ||||||
|  |         name: None, | ||||||
|  |         tool_calls: None, | ||||||
|  |         tool_call_id: None, | ||||||
|  |     }]; | ||||||
|  |  | ||||||
|  |     println!("Sending request to OpenRouter..."); | ||||||
|  |     let response = CompletionRequestBuilder::new( | ||||||
|  |         &mut *provider, | ||||||
|  |         "openai/gpt-oss-120b".to_string(), // Model name as specified by the user | ||||||
|  |         messages, | ||||||
|  |         provider_type, // Pass the provider_type | ||||||
|  |     ) | ||||||
|  |     .temperature(1.0) | ||||||
|  |     .max_tokens(8192) | ||||||
|  |     .top_p(1.0) | ||||||
|  |     .reasoning_effort("medium") | ||||||
|  |     .stream(false) | ||||||
|  |     .openrouter_options(|builder| { | ||||||
|  |         builder.provider( | ||||||
|  |             codemonkey::OpenRouterProviderOptionsBuilder::new() | ||||||
|  |                 .order(vec!["cerebras"]) | ||||||
|  |                 .build(), | ||||||
|  |         ) | ||||||
|  |     }) | ||||||
|  |     .completion() | ||||||
|  |     .await?; | ||||||
|  |  | ||||||
|  |     for choice in response.choices { | ||||||
|  |         if let Some(content) = choice.message.content { | ||||||
|  |             print!("{}", content); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     println!(); | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								examples_rust/ai/run.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										13
									
								
								examples_rust/ai/run.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | #!/bin/bash | ||||||
|  | set -e | ||||||
|  |  | ||||||
|  | # Change to directory where this script is located | ||||||
|  | cd "$(dirname "${BASH_SOURCE[0]}")" | ||||||
|  |  | ||||||
|  | source ../../config/myenv.sh | ||||||
|  |  | ||||||
|  | # Build the example | ||||||
|  | cargo build | ||||||
|  |  | ||||||
|  | # Run the example | ||||||
|  | cargo run --bin openrouter_example | ||||||
							
								
								
									
										10
									
								
								packages/ai/codemonkey/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/ai/codemonkey/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | [package] | ||||||
|  | name = "codemonkey" | ||||||
|  | version = "0.1.0" | ||||||
|  | edition = "2021" | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | tokio = { version = "1", features = ["full"] } | ||||||
|  | async-trait = "0.1.80" | ||||||
|  | openrouter-rs = "0.4.5" | ||||||
|  | serde = { version = "1.0", features = ["derive"] } | ||||||
							
								
								
									
										216
									
								
								packages/ai/codemonkey/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								packages/ai/codemonkey/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | |||||||
|  | use async_trait::async_trait; | ||||||
|  | use openrouter_rs::{OpenRouterClient, api::chat::{ChatCompletionRequest, Message}, types::completion::CompletionsResponse}; | ||||||
|  | use std::env; | ||||||
|  | use std::error::Error; | ||||||
|  |  | ||||||
|  | // Re-export MessageRole for easier use in client code | ||||||
|  | pub use openrouter_rs::types::Role as MessageRole;  | ||||||
|  |  | ||||||
|  | #[async_trait] | ||||||
|  | pub trait AIProvider { | ||||||
|  |     async fn completion( | ||||||
|  |         &mut self, | ||||||
|  |         request: CompletionRequest, | ||||||
|  |     ) -> Result<CompletionsResponse, Box<dyn Error>>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct CompletionRequest { | ||||||
|  |     pub model: String, | ||||||
|  |     pub messages: Vec<Message>, | ||||||
|  |     pub temperature: Option<f64>, | ||||||
|  |     pub max_tokens: Option<i64>, | ||||||
|  |     pub top_p: Option<f64>, | ||||||
|  |     pub stream: Option<bool>, | ||||||
|  |     pub stop: Option<Vec<String>>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct CompletionRequestBuilder<'a> { | ||||||
|  |     provider: &'a mut dyn AIProvider, | ||||||
|  |     model: String, | ||||||
|  |     messages: Vec<Message>, | ||||||
|  |     temperature: Option<f64>, | ||||||
|  |     max_tokens: Option<i64>, | ||||||
|  |     top_p: Option<f64>, | ||||||
|  |     stream: Option<bool>, | ||||||
|  |     stop: Option<Vec<String>>, | ||||||
|  |     provider_type: AIProviderType, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a> CompletionRequestBuilder<'a> { | ||||||
|  |     pub fn new(provider: &'a mut dyn AIProvider, model: String, messages: Vec<Message>, provider_type: AIProviderType) -> Self { | ||||||
|  |         Self { | ||||||
|  |             provider, | ||||||
|  |             model, | ||||||
|  |             messages, | ||||||
|  |             temperature: None, | ||||||
|  |             max_tokens: None, | ||||||
|  |             top_p: None, | ||||||
|  |             stream: None, | ||||||
|  |             stop: None, | ||||||
|  |             provider_type, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn temperature(mut self, temperature: f64) -> Self { | ||||||
|  |         self.temperature = Some(temperature); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn max_tokens(mut self, max_tokens: i64) -> Self { | ||||||
|  |         self.max_tokens = Some(max_tokens); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn top_p(mut self, top_p: f64) -> Self { | ||||||
|  |         self.top_p = Some(top_p); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn stream(mut self, stream: bool) -> Self { | ||||||
|  |         self.stream = Some(stream); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub fn stop(mut self, stop: Vec<String>) -> Self { | ||||||
|  |         self.stop = Some(stop); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     pub async fn completion(self) -> Result<CompletionsResponse, Box<dyn Error>> { | ||||||
|  |         let request = CompletionRequest { | ||||||
|  |             model: self.model, | ||||||
|  |             messages: self.messages, | ||||||
|  |             temperature: self.temperature, | ||||||
|  |             max_tokens: self.max_tokens, | ||||||
|  |             top_p: self.top_p, | ||||||
|  |             stream: self.stream, | ||||||
|  |             stop: self.stop, | ||||||
|  |         }; | ||||||
|  |         self.provider.completion(request).await | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct GroqAIProvider { | ||||||
|  |     client: OpenRouterClient, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[async_trait] | ||||||
|  | impl AIProvider for GroqAIProvider { | ||||||
|  |     async fn completion( | ||||||
|  |         &mut self, | ||||||
|  |         request: CompletionRequest, | ||||||
|  |     ) -> Result<CompletionsResponse, Box<dyn Error>> { | ||||||
|  |         let chat_request = ChatCompletionRequest::builder() | ||||||
|  |             .model(request.model) | ||||||
|  |             .messages(request.messages) | ||||||
|  |             .temperature(request.temperature.unwrap_or(1.0)) | ||||||
|  |             .max_tokens(request.max_tokens.map(|x| x as u32).unwrap_or(2048)) | ||||||
|  |             .top_p(request.top_p.unwrap_or(1.0)) | ||||||
|  |             .build()?; | ||||||
|  |  | ||||||
|  |         let result = self.client.send_chat_completion(&chat_request).await?; | ||||||
|  |         Ok(result) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct OpenAIProvider { | ||||||
|  |     client: OpenRouterClient, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[async_trait] | ||||||
|  | impl AIProvider for OpenAIProvider { | ||||||
|  |     async fn completion( | ||||||
|  |         &mut self, | ||||||
|  |         request: CompletionRequest, | ||||||
|  |     ) -> Result<CompletionsResponse, Box<dyn Error>> { | ||||||
|  |         let chat_request = ChatCompletionRequest::builder() | ||||||
|  |             .model(request.model) | ||||||
|  |             .messages(request.messages) | ||||||
|  |             .temperature(request.temperature.unwrap_or(1.0)) | ||||||
|  |             .max_tokens(request.max_tokens.map(|x| x as u32).unwrap_or(2048)) | ||||||
|  |             .top_p(request.top_p.unwrap_or(1.0)) | ||||||
|  |             .build()?; | ||||||
|  |  | ||||||
|  |         let result = self.client.send_chat_completion(&chat_request).await?; | ||||||
|  |         Ok(result) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct OpenRouterAIProvider { | ||||||
|  |     client: OpenRouterClient, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[async_trait] | ||||||
|  | impl AIProvider for OpenRouterAIProvider { | ||||||
|  |     async fn completion( | ||||||
|  |         &mut self, | ||||||
|  |         request: CompletionRequest, | ||||||
|  |     ) -> Result<CompletionsResponse, Box<dyn Error>> { | ||||||
|  |         let chat_request = ChatCompletionRequest::builder() | ||||||
|  |             .model(request.model) | ||||||
|  |             .messages(request.messages) | ||||||
|  |             .temperature(request.temperature.unwrap_or(1.0)) | ||||||
|  |             .max_tokens(request.max_tokens.map(|x| x as u32).unwrap_or(2048)) | ||||||
|  |             .top_p(request.top_p.unwrap_or(1.0)) | ||||||
|  |             .build()?; | ||||||
|  |  | ||||||
|  |         let result = self.client.send_chat_completion(&chat_request).await?; | ||||||
|  |         Ok(result) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub struct CerebrasAIProvider { | ||||||
|  |     client: OpenRouterClient, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[async_trait] | ||||||
|  | impl AIProvider for CerebrasAIProvider { | ||||||
|  |     async fn completion( | ||||||
|  |         &mut self, | ||||||
|  |         request: CompletionRequest, | ||||||
|  |     ) -> Result<CompletionsResponse, Box<dyn Error>> { | ||||||
|  |         let chat_request = ChatCompletionRequest::builder() | ||||||
|  |             .model(request.model) | ||||||
|  |             .messages(request.messages) | ||||||
|  |             .temperature(request.temperature.unwrap_or(1.0)) | ||||||
|  |             .max_tokens(request.max_tokens.map(|x| x as u32).unwrap_or(2048)) | ||||||
|  |             .top_p(request.top_p.unwrap_or(1.0)) | ||||||
|  |             .build()?; | ||||||
|  |  | ||||||
|  |         let result = self.client.send_chat_completion(&chat_request).await?; | ||||||
|  |         Ok(result) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[derive(PartialEq)] | ||||||
|  | pub enum AIProviderType { | ||||||
|  |     Groq, | ||||||
|  |     OpenAI, | ||||||
|  |     OpenRouter, | ||||||
|  |     Cerebras, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | pub fn create_ai_provider(provider_type: AIProviderType) -> Result<(Box<dyn AIProvider>, AIProviderType), Box<dyn Error>> { | ||||||
|  |     match provider_type { | ||||||
|  |         AIProviderType::Groq => { | ||||||
|  |             let api_key = env::var("GROQ_API_KEY")?; | ||||||
|  |             let client = OpenRouterClient::builder().api_key(api_key).build()?; | ||||||
|  |             Ok((Box::new(GroqAIProvider { client }), AIProviderType::Groq)) | ||||||
|  |         } | ||||||
|  |         AIProviderType::OpenAI => { | ||||||
|  |             let api_key = env::var("OPENAI_API_KEY")?; | ||||||
|  |             let client = OpenRouterClient::builder().api_key(api_key).build()?; | ||||||
|  |             Ok((Box::new(OpenAIProvider { client }), AIProviderType::OpenAI)) | ||||||
|  |         } | ||||||
|  |         AIProviderType::OpenRouter => { | ||||||
|  |             let api_key = env::var("OPENROUTER_API_KEY")?; | ||||||
|  |             let client = OpenRouterClient::builder().api_key(api_key).build()?; | ||||||
|  |             Ok((Box::new(OpenRouterAIProvider { client }), AIProviderType::OpenRouter)) | ||||||
|  |         } | ||||||
|  |         AIProviderType::Cerebras => { | ||||||
|  |             let api_key = env::var("CEREBRAS_API_KEY")?; | ||||||
|  |             let client = OpenRouterClient::builder().api_key(api_key).build()?; | ||||||
|  |             Ok((Box::new(CerebrasAIProvider { client }), AIProviderType::Cerebras)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1004,7 +1004,7 @@ impl OrderServerProduct { | |||||||
|             }) |             }) | ||||||
|             .with_get("traffic", |o: &mut OrderServerProduct| o.traffic.clone()) |             .with_get("traffic", |o: &mut OrderServerProduct| o.traffic.clone()) | ||||||
|             .with_get("dist", |o: &mut OrderServerProduct| o.dist.clone()) |             .with_get("dist", |o: &mut OrderServerProduct| o.dist.clone()) | ||||||
|             .with_get("arch", |o: &mut OrderServerProduct| o.arch.clone()) |             .with_get("arch", |o: &mut OrderServerProduct| o.dist.clone()) | ||||||
|             .with_get("lang", |o: &mut OrderServerProduct| o.lang.clone()) |             .with_get("lang", |o: &mut OrderServerProduct| o.lang.clone()) | ||||||
|             .with_get("location", |o: &mut OrderServerProduct| o.location.clone()) |             .with_get("location", |o: &mut OrderServerProduct| o.location.clone()) | ||||||
|             .with_get("prices", |o: &mut OrderServerProduct| o.prices.clone()) |             .with_get("prices", |o: &mut OrderServerProduct| o.prices.clone()) | ||||||
| @@ -1027,7 +1027,7 @@ impl fmt::Display for OrderServerProduct { | |||||||
|         table.add_row(row!["Distributions", self.dist.join(", ")]); |         table.add_row(row!["Distributions", self.dist.join(", ")]); | ||||||
|         table.add_row(row![ |         table.add_row(row![ | ||||||
|             "Architectures", |             "Architectures", | ||||||
|             self.arch.as_deref().unwrap_or_default().join(", ") |             self.dist.join(", ") | ||||||
|         ]); |         ]); | ||||||
|         table.add_row(row!["Languages", self.lang.join(", ")]); |         table.add_row(row!["Languages", self.lang.join(", ")]); | ||||||
|         table.add_row(row!["Locations", self.location.join(", ")]); |         table.add_row(row!["Locations", self.location.join(", ")]); | ||||||
| @@ -1270,7 +1270,7 @@ impl AuctionServerProduct { | |||||||
|             }) |             }) | ||||||
|             .with_get("traffic", |p: &mut AuctionServerProduct| p.traffic.clone()) |             .with_get("traffic", |p: &mut AuctionServerProduct| p.traffic.clone()) | ||||||
|             .with_get("dist", |p: &mut AuctionServerProduct| p.dist.clone()) |             .with_get("dist", |p: &mut AuctionServerProduct| p.dist.clone()) | ||||||
|             .with_get("arch", |p: &mut AuctionServerProduct| p.arch.clone()) |             .with_get("arch", |p: &mut AuctionServerProduct| p.dist.clone()) | ||||||
|             .with_get("lang", |p: &mut AuctionServerProduct| p.lang.clone()) |             .with_get("lang", |p: &mut AuctionServerProduct| p.lang.clone()) | ||||||
|             .with_get("cpu", |p: &mut AuctionServerProduct| p.cpu.clone()) |             .with_get("cpu", |p: &mut AuctionServerProduct| p.cpu.clone()) | ||||||
|             .with_get("cpu_benchmark", |p: &mut AuctionServerProduct| { |             .with_get("cpu_benchmark", |p: &mut AuctionServerProduct| { | ||||||
| @@ -1328,7 +1328,7 @@ impl fmt::Display for AuctionServerProduct { | |||||||
|         table.add_row(row!["Distributions", self.dist.join(", ")]); |         table.add_row(row!["Distributions", self.dist.join(", ")]); | ||||||
|         table.add_row(row![ |         table.add_row(row![ | ||||||
|             "Architectures", |             "Architectures", | ||||||
|             self.arch.as_deref().unwrap_or_default().join(", ") |             self.dist.join(", ") | ||||||
|         ]); |         ]); | ||||||
|         table.add_row(row!["Languages", self.lang.join(", ")]); |         table.add_row(row!["Languages", self.lang.join(", ")]); | ||||||
|         table.add_row(row!["CPU", self.cpu.clone()]); |         table.add_row(row!["CPU", self.cpu.clone()]); | ||||||
| @@ -1486,7 +1486,7 @@ impl fmt::Display for AuctionTransaction { | |||||||
|         table.add_row(row!["Product Distributions", self.product.dist.clone()]); |         table.add_row(row!["Product Distributions", self.product.dist.clone()]); | ||||||
|         table.add_row(row![ |         table.add_row(row![ | ||||||
|             "Product Architectures", |             "Product Architectures", | ||||||
|             self.product.arch.as_deref().unwrap_or("N/A") |             &self.product.dist | ||||||
|         ]); |         ]); | ||||||
|         table.add_row(row!["Product Languages", self.product.lang.clone()]); |         table.add_row(row!["Product Languages", self.product.lang.clone()]); | ||||||
|         table.add_row(row!["Product CPU", self.product.cpu.clone()]); |         table.add_row(row!["Product CPU", self.product.cpu.clone()]); | ||||||
| @@ -1569,7 +1569,7 @@ impl AuctionTransactionProduct { | |||||||
|             }) |             }) | ||||||
|             .with_get("dist", |p: &mut AuctionTransactionProduct| p.dist.clone()) |             .with_get("dist", |p: &mut AuctionTransactionProduct| p.dist.clone()) | ||||||
|             .with_get("arch", |p: &mut AuctionTransactionProduct| { |             .with_get("arch", |p: &mut AuctionTransactionProduct| { | ||||||
|                 p.arch.clone().unwrap_or_default() |                 p.dist.clone() | ||||||
|             }) |             }) | ||||||
|             .with_get("lang", |p: &mut AuctionTransactionProduct| p.lang.clone()) |             .with_get("lang", |p: &mut AuctionTransactionProduct| p.lang.clone()) | ||||||
|             .with_get("cpu", |p: &mut AuctionTransactionProduct| p.cpu.clone()) |             .with_get("cpu", |p: &mut AuctionTransactionProduct| p.cpu.clone()) | ||||||
|   | |||||||
| @@ -100,7 +100,7 @@ pub fn pretty_print_auction_server_products(products: rhai::Array) { | |||||||
|                 product.description.join(", "), |                 product.description.join(", "), | ||||||
|                 product.traffic, |                 product.traffic, | ||||||
|                 product.dist.join(", "), |                 product.dist.join(", "), | ||||||
|                 product.arch.as_deref().unwrap_or_default().join(", "), |                 product.dist.join(", "), | ||||||
|                 product.lang.join(", "), |                 product.lang.join(", "), | ||||||
|                 product.cpu, |                 product.cpu, | ||||||
|                 product.cpu_benchmark, |                 product.cpu_benchmark, | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								packages/clients/rfsclient/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								packages/clients/rfsclient/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | [package] | ||||||
|  | name = "sal-rfs-client" | ||||||
|  | version = "0.1.0" | ||||||
|  | edition = "2021" | ||||||
|  | description = "SAL RFS Client - Client library for Remote File System server" | ||||||
|  | repository = "https://git.threefold.info/herocode/sal" | ||||||
|  | license = "Apache-2.0" | ||||||
|  | keywords = ["rfs", "client", "filesystem", "remote"] | ||||||
|  | categories = ["filesystem", "api-bindings"] | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | openapi = { path = "./openapi" } | ||||||
|  | thiserror.workspace = true | ||||||
|  | url.workspace = true | ||||||
|  | reqwest = { workspace = true, features = ["json", "multipart"] } | ||||||
|  | tokio = { workspace = true, features = ["full"] } | ||||||
|  | serde = { workspace = true, features = ["derive"] } | ||||||
|  | serde_json.workspace = true | ||||||
|  | log.workspace = true | ||||||
|  | bytes.workspace = true | ||||||
|  | futures.workspace = true | ||||||
|  | rhai.workspace = true | ||||||
|  | lazy_static.workspace = true | ||||||
|  |  | ||||||
|  | [dev-dependencies] | ||||||
|  | tempfile = "3.0" | ||||||
							
								
								
									
										195
									
								
								packages/clients/rfsclient/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								packages/clients/rfsclient/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,195 @@ | |||||||
|  | # RFS Client | ||||||
|  |  | ||||||
|  | A Rust client library for interacting with the Remote File System (RFS) server. | ||||||
|  |  | ||||||
|  | ## Overview | ||||||
|  |  | ||||||
|  | This client library provides a user-friendly wrapper around the OpenAPI-generated client code. It offers high-level abstractions for common operations such as: | ||||||
|  |  | ||||||
|  | - Authentication and session management | ||||||
|  | - File uploads and downloads with progress tracking | ||||||
|  | - Block-level operations and verification | ||||||
|  | - FList creation, monitoring, and management | ||||||
|  | - Timeout configuration and error handling | ||||||
|  |  | ||||||
|  | ## Structure | ||||||
|  |  | ||||||
|  | The library is organized as follows: | ||||||
|  |  | ||||||
|  | - `client.rs`: Main client implementation with methods for interacting with the RFS server | ||||||
|  | - `error.rs`: Error types and handling | ||||||
|  | - `types.rs`: Type definitions and utilities | ||||||
|  |  | ||||||
|  | ## Quick Start | ||||||
|  |  | ||||||
|  | ```rust | ||||||
|  | use rfs_client::RfsClient; | ||||||
|  | use rfs_client::types::{ClientConfig, Credentials}; | ||||||
|  |  | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     // Create a client with custom configuration | ||||||
|  |     let config = ClientConfig { | ||||||
|  |         base_url: "http://localhost:8080".to_string(), | ||||||
|  |         credentials: Some(Credentials { | ||||||
|  |             username: "user".to_string(), | ||||||
|  |             password: "password".to_string(), | ||||||
|  |         }), | ||||||
|  |         timeout_seconds: 60, | ||||||
|  |     }; | ||||||
|  |      | ||||||
|  |     let mut client = RfsClient::new(config); | ||||||
|  |      | ||||||
|  |     // Authenticate | ||||||
|  |     client.authenticate().await?; | ||||||
|  |     println!("Authentication successful"); | ||||||
|  |      | ||||||
|  |     // Upload a file | ||||||
|  |     let file_path = "/path/to/file.txt"; | ||||||
|  |     let file_hash = client.upload_file(file_path, None).await?; | ||||||
|  |     println!("File uploaded with hash: {}", file_hash); | ||||||
|  |      | ||||||
|  |     // Download the file | ||||||
|  |     let output_path = "/path/to/output.txt"; | ||||||
|  |     client.download_file(&file_hash, output_path, None).await?; | ||||||
|  |     println!("File downloaded to {}", output_path); | ||||||
|  |      | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Feature Examples | ||||||
|  |  | ||||||
|  | ### Authentication | ||||||
|  |  | ||||||
|  | ```rust | ||||||
|  | // Create a client with authentication | ||||||
|  | let config = ClientConfig { | ||||||
|  |     base_url: "http://localhost:8080".to_string(), | ||||||
|  |     credentials: Some(Credentials { | ||||||
|  |         username: "user".to_string(), | ||||||
|  |         password: "password".to_string(), | ||||||
|  |     }), | ||||||
|  |     timeout_seconds: 30, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | let mut client = RfsClient::new(config); | ||||||
|  |  | ||||||
|  | // Authenticate with the server | ||||||
|  | client.authenticate().await?; | ||||||
|  | if client.is_authenticated() { | ||||||
|  |     println!("Authentication successful"); | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### File Management | ||||||
|  |  | ||||||
|  | ```rust | ||||||
|  | // Upload a file with options | ||||||
|  | let upload_options = UploadOptions { | ||||||
|  |     chunk_size: Some(1024 * 1024), // 1MB chunks | ||||||
|  |     verify: true, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | let file_hash = client.upload_file("/path/to/file.txt", Some(upload_options)).await?; | ||||||
|  |  | ||||||
|  | // Download the file | ||||||
|  | let download_options = DownloadOptions { | ||||||
|  |     verify: true, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | client.download_file(&file_hash, "/path/to/output.txt", Some(download_options)).await?; | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### FList Operations | ||||||
|  |  | ||||||
|  | ```rust | ||||||
|  | // Create an FList from a Docker image | ||||||
|  | let options = FlistOptions { | ||||||
|  |     auth: None, | ||||||
|  |     username: None, | ||||||
|  |     password: None, | ||||||
|  |     email: None, | ||||||
|  |     server_address: Some("docker.io".to_string()), | ||||||
|  |     identity_token: None, | ||||||
|  |     registry_token: None, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | let job_id = client.create_flist("alpine:latest", Some(options)).await?; | ||||||
|  |  | ||||||
|  | // Wait for FList creation with progress tracking | ||||||
|  | let wait_options = WaitOptions { | ||||||
|  |     timeout_seconds: 60, | ||||||
|  |     poll_interval_ms: 1000, | ||||||
|  |     progress_callback: Some(Box::new(|state| { | ||||||
|  |         println!("Progress: FList state is now {:?}", state); | ||||||
|  |     })), | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | let final_state = client.wait_for_flist_creation(&job_id, Some(wait_options)).await?; | ||||||
|  |  | ||||||
|  | // List available FLists | ||||||
|  | let flists = client.list_flists().await?; | ||||||
|  |  | ||||||
|  | // Preview an FList | ||||||
|  | let preview = client.preview_flist("flists/user/alpine-latest.fl").await?; | ||||||
|  |  | ||||||
|  | // Download an FList | ||||||
|  | client.download_flist("flists/user/alpine-latest.fl", "/tmp/downloaded_flist.fl").await?; | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Block Management | ||||||
|  |  | ||||||
|  | ```rust | ||||||
|  | // List blocks | ||||||
|  | let blocks_list = client.list_blocks(None).await?; | ||||||
|  |  | ||||||
|  | // Check if a block exists | ||||||
|  | let exists = client.check_block("block_hash").await?; | ||||||
|  |  | ||||||
|  | // Get block content | ||||||
|  | let block_content = client.get_block("block_hash").await?; | ||||||
|  |  | ||||||
|  | // Upload a block | ||||||
|  | let block_hash = client.upload_block("file_hash", 0, data).await?; | ||||||
|  |  | ||||||
|  | // Verify blocks | ||||||
|  | let request = VerifyBlocksRequest { blocks: verify_blocks }; | ||||||
|  | let verify_result = client.verify_blocks(request).await?; | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Complete Examples | ||||||
|  |  | ||||||
|  | For more detailed examples, check the `examples` directory: | ||||||
|  |  | ||||||
|  | - `authentication.rs`: Authentication and health check examples | ||||||
|  | - `file_management.rs`: File upload and download with verification | ||||||
|  | - `flist_operations.rs`: Complete FList creation, monitoring, listing, preview, and download | ||||||
|  | - `block_management.rs`: Block-level operations including listing, verification, and upload | ||||||
|  | - `wait_for_flist.rs`: Advanced FList creation with progress monitoring | ||||||
|  |  | ||||||
|  | Run an example with: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | cargo run --example flist_operations | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Development | ||||||
|  |  | ||||||
|  | This library wraps the OpenAPI-generated client located in the `openapi` directory. The OpenAPI client was generated using the OpenAPI Generator CLI. | ||||||
|  |  | ||||||
|  | To build the library: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | cargo build | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | To run tests: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | cargo test -- --test-threads=1 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## License | ||||||
|  |  | ||||||
|  | MIT | ||||||
							
								
								
									
										42
									
								
								packages/clients/rfsclient/examples/authentication.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								packages/clients/rfsclient/examples/authentication.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | use sal_rfs_client::types::{ClientConfig, Credentials}; | ||||||
|  | use sal_rfs_client::RfsClient; | ||||||
|  |  | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     // Create a client with authentication credentials | ||||||
|  |     let config = ClientConfig { | ||||||
|  |         base_url: "http://localhost:8080".to_string(), | ||||||
|  |         credentials: Some(Credentials { | ||||||
|  |             username: "user".to_string(), | ||||||
|  |             password: "password".to_string(), | ||||||
|  |         }), | ||||||
|  |         timeout_seconds: 30, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let mut client = RfsClient::new(config); | ||||||
|  |     println!("Client created with authentication credentials"); | ||||||
|  |  | ||||||
|  |     // Authenticate with the server | ||||||
|  |     client.authenticate().await?; | ||||||
|  |     if client.is_authenticated() { | ||||||
|  |         println!("Authentication successful"); | ||||||
|  |     } else { | ||||||
|  |         println!("Authentication failed"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Create a client without authentication | ||||||
|  |     let config_no_auth = ClientConfig { | ||||||
|  |         base_url: "http://localhost:8080".to_string(), | ||||||
|  |         credentials: None, | ||||||
|  |         timeout_seconds: 30, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let client_no_auth = RfsClient::new(config_no_auth); | ||||||
|  |     println!("Client created without authentication credentials"); | ||||||
|  |  | ||||||
|  |     // Check health endpoint (doesn't require authentication) | ||||||
|  |     let health = client_no_auth.health_check().await?; | ||||||
|  |     println!("Server health: {:?}", health); | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
							
								
								
									
										153
									
								
								packages/clients/rfsclient/examples/block_management.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								packages/clients/rfsclient/examples/block_management.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | |||||||
|  | use openapi::models::{VerifyBlock, VerifyBlocksRequest}; | ||||||
|  | use sal_rfs_client::types::{ClientConfig, Credentials}; | ||||||
|  | use sal_rfs_client::RfsClient; | ||||||
|  |  | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     // Create a client with authentication | ||||||
|  |     let config = ClientConfig { | ||||||
|  |         base_url: "http://localhost:8080".to_string(), | ||||||
|  |         credentials: Some(Credentials { | ||||||
|  |             username: "user".to_string(), | ||||||
|  |             password: "password".to_string(), | ||||||
|  |         }), | ||||||
|  |         timeout_seconds: 60, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let mut client = RfsClient::new(config); | ||||||
|  |  | ||||||
|  |     // Authenticate with the server | ||||||
|  |     client.authenticate().await?; | ||||||
|  |     println!("Authentication successful"); | ||||||
|  |  | ||||||
|  |     // Create a test file to upload for block testing | ||||||
|  |     let test_file_path = "/tmp/block_test.txt"; | ||||||
|  |     let test_content = "This is a test file for RFS client block management"; | ||||||
|  |     std::fs::write(test_file_path, test_content)?; | ||||||
|  |     println!("Created test file at {}", test_file_path); | ||||||
|  |  | ||||||
|  |     // Upload the file to get blocks | ||||||
|  |     println!("Uploading file to get blocks..."); | ||||||
|  |     let file_hash = client.upload_file(test_file_path, None).await?; | ||||||
|  |     println!("File uploaded with hash: {}", file_hash); | ||||||
|  |  | ||||||
|  |     // Get blocks by file hash | ||||||
|  |     println!("Getting blocks for file hash: {}", file_hash); | ||||||
|  |     let blocks = client.get_blocks_by_hash(&file_hash).await?; | ||||||
|  |     println!("Found {} blocks for the file", blocks.blocks.len()); | ||||||
|  |  | ||||||
|  |     // Print block information | ||||||
|  |     for (i, block_data) in blocks.blocks.iter().enumerate() { | ||||||
|  |         println!( | ||||||
|  |             "Block {}: Hash={}, Index={}", | ||||||
|  |             i, block_data.hash, block_data.index | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Verify blocks with complete information | ||||||
|  |     println!("Verifying blocks..."); | ||||||
|  |  | ||||||
|  |     // Create a list of VerifyBlock objects with complete information | ||||||
|  |     let verify_blocks = blocks | ||||||
|  |         .blocks | ||||||
|  |         .iter() | ||||||
|  |         .map(|block| { | ||||||
|  |             VerifyBlock { | ||||||
|  |                 block_hash: block.hash.clone(), | ||||||
|  |                 block_index: block.index, | ||||||
|  |                 file_hash: file_hash.clone(), // Using the actual file hash | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         .collect::<Vec<_>>(); | ||||||
|  |  | ||||||
|  |     // Create the request with the complete block information | ||||||
|  |     for block in verify_blocks.iter() { | ||||||
|  |         println!("Block: {}", block.block_hash); | ||||||
|  |         println!("Block index: {}", block.block_index); | ||||||
|  |         println!("File hash: {}", block.file_hash); | ||||||
|  |     } | ||||||
|  |     let request = VerifyBlocksRequest { | ||||||
|  |         blocks: verify_blocks, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // Send the verification request | ||||||
|  |     let verify_result = client.verify_blocks(request).await?; | ||||||
|  |     println!( | ||||||
|  |         "Verification result: {} missing blocks", | ||||||
|  |         verify_result.missing.len() | ||||||
|  |     ); | ||||||
|  |     for block in verify_result.missing.iter() { | ||||||
|  |         println!("Missing block: {}", block); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // List blocks (list_blocks_handler) | ||||||
|  |     println!("\n1. Listing all blocks with pagination..."); | ||||||
|  |     let blocks_list = client.list_blocks(None).await?; | ||||||
|  |     println!("Server has {} blocks in total", blocks_list.len()); | ||||||
|  |     if !blocks_list.is_empty() { | ||||||
|  |         let first_few = blocks_list | ||||||
|  |             .iter() | ||||||
|  |             .take(3) | ||||||
|  |             .map(|s| s.as_str()) | ||||||
|  |             .collect::<Vec<_>>() | ||||||
|  |             .join(", "); | ||||||
|  |         println!("First few blocks: {}", first_few); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Check if a block exists (check_block_handler) | ||||||
|  |     if !blocks.blocks.is_empty() { | ||||||
|  |         let block_to_check = &blocks.blocks[0].hash; | ||||||
|  |         println!("\n2. Checking if block exists: {}", block_to_check); | ||||||
|  |         let exists = client.check_block(block_to_check).await?; | ||||||
|  |         println!("Block exists: {}", exists); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Get block downloads statistics (get_block_downloads_handler) | ||||||
|  |     if !blocks.blocks.is_empty() { | ||||||
|  |         let block_to_check = &blocks.blocks[0].hash; | ||||||
|  |         println!( | ||||||
|  |             "\n3. Getting download statistics for block: {}", | ||||||
|  |             block_to_check | ||||||
|  |         ); | ||||||
|  |         let downloads = client.get_block_downloads(block_to_check).await?; | ||||||
|  |         println!( | ||||||
|  |             "Block has been downloaded {} times", | ||||||
|  |             downloads.downloads_count | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Get a specific block content (get_block_handler) | ||||||
|  |     if !blocks.blocks.is_empty() { | ||||||
|  |         let block_to_get = &blocks.blocks[0].hash; | ||||||
|  |         println!("\n4. Getting content for block: {}", block_to_get); | ||||||
|  |         let block_content = client.get_block(block_to_get).await?; | ||||||
|  |         println!("Retrieved block with {} bytes", block_content.len()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Get user blocks (get_user_blocks_handler) | ||||||
|  |     println!("\n6. Listing user blocks..."); | ||||||
|  |     let user_blocks = client.get_user_blocks(Some(1), Some(10)).await?; | ||||||
|  |     println!( | ||||||
|  |         "User has {} blocks (showing page 1 with 10 per page)", | ||||||
|  |         user_blocks.total | ||||||
|  |     ); | ||||||
|  |     for block in user_blocks.blocks.iter().take(3) { | ||||||
|  |         println!("  - Block: {}, Size: {}", block.hash, block.size); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Upload a block (upload_block_handler) | ||||||
|  |     println!("\n7. Uploading a new test block..."); | ||||||
|  |     let test_block_data = b"This is test block data for direct block upload"; | ||||||
|  |     let new_file_hash = "test_file_hash_for_block_upload"; | ||||||
|  |     let block_index = 0; | ||||||
|  |     let block_hash = client | ||||||
|  |         .upload_block(new_file_hash, block_index, test_block_data.to_vec()) | ||||||
|  |         .await?; | ||||||
|  |     println!("Uploaded block with hash: {}", block_hash); | ||||||
|  |  | ||||||
|  |     // Clean up | ||||||
|  |     std::fs::remove_file(test_file_path)?; | ||||||
|  |     println!("Test file cleaned up"); | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								packages/clients/rfsclient/examples/file_management.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								packages/clients/rfsclient/examples/file_management.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | use sal_rfs_client::types::{ClientConfig, Credentials, DownloadOptions, UploadOptions}; | ||||||
|  | use sal_rfs_client::RfsClient; | ||||||
|  |  | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     // Create a client with authentication | ||||||
|  |     let config = ClientConfig { | ||||||
|  |         base_url: "http://localhost:8080".to_string(), | ||||||
|  |         credentials: Some(Credentials { | ||||||
|  |             username: "user".to_string(), | ||||||
|  |             password: "password".to_string(), | ||||||
|  |         }), | ||||||
|  |         timeout_seconds: 60, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let mut client = RfsClient::new(config); | ||||||
|  |  | ||||||
|  |     // Authenticate with the server | ||||||
|  |     client.authenticate().await?; | ||||||
|  |     println!("Authentication successful"); | ||||||
|  |  | ||||||
|  |     // Create a test file to upload | ||||||
|  |     let test_file_path = "/tmp/test_upload.txt"; | ||||||
|  |     std::fs::write(test_file_path, "This is a test file for RFS client upload")?; | ||||||
|  |     println!("Created test file at {}", test_file_path); | ||||||
|  |  | ||||||
|  |     // Upload the file with options | ||||||
|  |     println!("Uploading file..."); | ||||||
|  |     let upload_options = UploadOptions { | ||||||
|  |         chunk_size: Some(1024 * 1024), // 1MB chunks | ||||||
|  |         verify: true, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let file_hash = client | ||||||
|  |         .upload_file(test_file_path, Some(upload_options)) | ||||||
|  |         .await?; | ||||||
|  |     println!("File uploaded with hash: {}", file_hash); | ||||||
|  |  | ||||||
|  |     // Download the file | ||||||
|  |     let download_path = "/tmp/test_download.txt"; | ||||||
|  |     println!("Downloading file to {}...", download_path); | ||||||
|  |  | ||||||
|  |     let download_options = DownloadOptions { verify: true }; | ||||||
|  |  | ||||||
|  |     client | ||||||
|  |         .download_file(&file_hash, download_path, Some(download_options)) | ||||||
|  |         .await?; | ||||||
|  |     println!("File downloaded to {}", download_path); | ||||||
|  |  | ||||||
|  |     // Verify the downloaded file matches the original | ||||||
|  |     let original_content = std::fs::read_to_string(test_file_path)?; | ||||||
|  |     let downloaded_content = std::fs::read_to_string(download_path)?; | ||||||
|  |  | ||||||
|  |     if original_content == downloaded_content { | ||||||
|  |         println!("File contents match! Download successful."); | ||||||
|  |     } else { | ||||||
|  |         println!("ERROR: File contents do not match!"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Clean up test files | ||||||
|  |     std::fs::remove_file(test_file_path)?; | ||||||
|  |     std::fs::remove_file(download_path)?; | ||||||
|  |     println!("Test files cleaned up"); | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
							
								
								
									
										176
									
								
								packages/clients/rfsclient/examples/flist_operations.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								packages/clients/rfsclient/examples/flist_operations.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,176 @@ | |||||||
|  | use sal_rfs_client::types::{ClientConfig, Credentials, FlistOptions, WaitOptions}; | ||||||
|  | use sal_rfs_client::RfsClient; | ||||||
|  |  | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     let parent_dir = "flists"; | ||||||
|  |     // Create a client with authentication | ||||||
|  |     let config = ClientConfig { | ||||||
|  |         base_url: "http://localhost:8080".to_string(), | ||||||
|  |         credentials: Some(Credentials { | ||||||
|  |             username: "user".to_string(), | ||||||
|  |             password: "password".to_string(), | ||||||
|  |         }), | ||||||
|  |         timeout_seconds: 60, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let mut client = RfsClient::new(config); | ||||||
|  |  | ||||||
|  |     // Authenticate with the server | ||||||
|  |     client.authenticate().await?; | ||||||
|  |     println!("Authentication successful"); | ||||||
|  |  | ||||||
|  |     println!("\n1. CREATE FLIST - Creating an FList from a Docker image"); | ||||||
|  |     let image_name = "alpine:latest"; | ||||||
|  |     println!("Creating FList for image: {}", image_name); | ||||||
|  |  | ||||||
|  |     // Use FlistOptions to specify additional parameters | ||||||
|  |     let options = FlistOptions { | ||||||
|  |         auth: None, | ||||||
|  |         username: None, | ||||||
|  |         password: None, | ||||||
|  |         email: None, | ||||||
|  |         server_address: Some("docker.io".to_string()), | ||||||
|  |         identity_token: None, | ||||||
|  |         registry_token: None, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // Create the FList and handle potential conflict error | ||||||
|  |     let job_id = match client.create_flist(&image_name, Some(options)).await { | ||||||
|  |         Ok(id) => { | ||||||
|  |             println!("FList creation started with job ID: {}", id); | ||||||
|  |             Some(id) | ||||||
|  |         } | ||||||
|  |         Err(e) => { | ||||||
|  |             if e.to_string().contains("Conflict") { | ||||||
|  |                 println!("FList already exists"); | ||||||
|  |                 None | ||||||
|  |             } else { | ||||||
|  |                 return Err(e.into()); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // 2. Check FList state if we have a job ID | ||||||
|  |     if let Some(job_id) = &job_id { | ||||||
|  |         println!("\n2. GET FLIST STATE - Checking FList creation state"); | ||||||
|  |         let state = client.get_flist_state(job_id).await?; | ||||||
|  |         println!("Current FList state: {:?}", state.flist_state); | ||||||
|  |  | ||||||
|  |         // 3. Wait for FList creation with progress reporting | ||||||
|  |         println!("\n3. WAIT FOR FLIST CREATION - Waiting for FList to be created with progress reporting"); | ||||||
|  |         let wait_options = WaitOptions { | ||||||
|  |             timeout_seconds: 60, // Shorter timeout for the example | ||||||
|  |             poll_interval_ms: 1000, | ||||||
|  |             progress_callback: Some(Box::new(|state| { | ||||||
|  |                 println!("Progress: FList state is now {:?}", state); | ||||||
|  |                 // No return value needed (returns unit type) | ||||||
|  |             })), | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // Wait for the FList to be created (with a timeout) | ||||||
|  |         match client | ||||||
|  |             .wait_for_flist_creation(job_id, Some(wait_options)) | ||||||
|  |             .await | ||||||
|  |         { | ||||||
|  |             Ok(final_state) => { | ||||||
|  |                 println!("FList creation completed with state: {:?}", final_state); | ||||||
|  |             } | ||||||
|  |             Err(e) => { | ||||||
|  |                 println!("Error waiting for FList creation: {}", e); | ||||||
|  |                 // Continue with the example even if waiting fails | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 4. List all available FLists | ||||||
|  |     println!("\n4. LIST FLISTS - Listing all available FLists"); | ||||||
|  |  | ||||||
|  |     // Variable to store the FList path for preview and download | ||||||
|  |     let mut flist_path_for_preview: Option<String> = None; | ||||||
|  |  | ||||||
|  |     match client.list_flists().await { | ||||||
|  |         Ok(flists) => { | ||||||
|  |             println!("Found {} FList categories", flists.len()); | ||||||
|  |  | ||||||
|  |             for (category, files) in &flists { | ||||||
|  |                 println!("Category: {}", category); | ||||||
|  |                 for file in files.iter().take(2) { | ||||||
|  |                     // Show only first 2 files per category | ||||||
|  |                     println!("  - {} (size: {} bytes)", file.name, file.size); | ||||||
|  |  | ||||||
|  |                     // Save the first FList path for preview | ||||||
|  |                     if flist_path_for_preview.is_none() { | ||||||
|  |                         let path = format!("{}/{}/{}", parent_dir, category, file.name); | ||||||
|  |                         flist_path_for_preview = Some(path); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 if files.len() > 2 { | ||||||
|  |                     println!("  - ... and {} more files", files.len() - 2); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             // 5. Preview an FList if we found one | ||||||
|  |             if let Some(ref flist_path) = flist_path_for_preview { | ||||||
|  |                 println!("\n5. PREVIEW FLIST - Previewing FList: {}", flist_path); | ||||||
|  |                 match client.preview_flist(flist_path).await { | ||||||
|  |                     Ok(preview) => { | ||||||
|  |                         println!("FList preview for {}:", flist_path); | ||||||
|  |                         println!("  - Checksum: {}", preview.checksum); | ||||||
|  |                         println!("  - Metadata: {}", preview.metadata); | ||||||
|  |  | ||||||
|  |                         // Display content (list of strings) | ||||||
|  |                         if !preview.content.is_empty() { | ||||||
|  |                             println!("  - Content entries:"); | ||||||
|  |                             for (i, entry) in preview.content.iter().enumerate().take(5) { | ||||||
|  |                                 println!("    {}. {}", i + 1, entry); | ||||||
|  |                             } | ||||||
|  |                             if preview.content.len() > 5 { | ||||||
|  |                                 println!("    ... and {} more entries", preview.content.len() - 5); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     Err(e) => println!("Error previewing FList: {}", e), | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 println!("No FLists available for preview"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Err(e) => println!("Error listing FLists: {}", e), | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // 6. DOWNLOAD FLIST - Downloading an FList to a local file | ||||||
|  |     if let Some(ref flist_path) = flist_path_for_preview { | ||||||
|  |         println!("\n6. DOWNLOAD FLIST - Downloading FList: {}", flist_path); | ||||||
|  |  | ||||||
|  |         // Create a temporary output path for the downloaded FList | ||||||
|  |         let output_path = "/tmp/downloaded_flist.fl"; | ||||||
|  |  | ||||||
|  |         match client.download_flist(flist_path, output_path).await { | ||||||
|  |             Ok(_) => { | ||||||
|  |                 println!("FList successfully downloaded to {}", output_path); | ||||||
|  |  | ||||||
|  |                 // Get file size | ||||||
|  |                 match std::fs::metadata(output_path) { | ||||||
|  |                     Ok(metadata) => println!("Downloaded file size: {} bytes", metadata.len()), | ||||||
|  |                     Err(e) => println!("Error getting file metadata: {}", e), | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Err(e) => println!("Error downloading FList: {}", e), | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         println!("\n6. DOWNLOAD FLIST - No FList available for download"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     println!("\nAll FList operations demonstrated:"); | ||||||
|  |     println!("1. create_flist - Create a new FList from a Docker image"); | ||||||
|  |     println!("2. get_flist_state - Check the state of an FList creation job"); | ||||||
|  |     println!( | ||||||
|  |         "3. wait_for_flist_creation - Wait for an FList to be created with progress reporting" | ||||||
|  |     ); | ||||||
|  |     println!("4. list_flists - List all available FLists"); | ||||||
|  |     println!("5. preview_flist - Preview the content of an FList"); | ||||||
|  |     println!("6. download_flist - Download an FList to a local file"); | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								packages/clients/rfsclient/examples/wait_for_flist.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								packages/clients/rfsclient/examples/wait_for_flist.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | use openapi::models::FlistState; | ||||||
|  | use sal_rfs_client::types::{ClientConfig, Credentials, WaitOptions}; | ||||||
|  | use sal_rfs_client::RfsClient; | ||||||
|  |  | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|  |     // Create a client with authentication | ||||||
|  |     let config = ClientConfig { | ||||||
|  |         base_url: "http://localhost:8080".to_string(), | ||||||
|  |         credentials: Some(Credentials { | ||||||
|  |             username: "user".to_string(), | ||||||
|  |             password: "password".to_string(), | ||||||
|  |         }), | ||||||
|  |         timeout_seconds: 60, | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     let mut client = RfsClient::new(config); | ||||||
|  |  | ||||||
|  |     // Authenticate with the server | ||||||
|  |     client.authenticate().await?; | ||||||
|  |     println!("Authentication successful"); | ||||||
|  |  | ||||||
|  |     // Create an FList from a Docker image | ||||||
|  |     let image_name = "redis:latest"; | ||||||
|  |     println!("Creating FList for image: {}", image_name); | ||||||
|  |  | ||||||
|  |     let job_id = client.create_flist(&image_name, None).await?; | ||||||
|  |     println!("FList creation started with job ID: {}", job_id); | ||||||
|  |  | ||||||
|  |     // Set up options for waiting with progress reporting | ||||||
|  |     let options = WaitOptions { | ||||||
|  |         timeout_seconds: 600,   // 10 minutes timeout | ||||||
|  |         poll_interval_ms: 2000, // Check every 2 seconds | ||||||
|  |         progress_callback: Some(Box::new(|state| match state { | ||||||
|  |             FlistState::FlistStateInProgress(info) => { | ||||||
|  |                 println!( | ||||||
|  |                     "Progress: {:.1}% - {}", | ||||||
|  |                     info.in_progress.progress, info.in_progress.msg | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |             FlistState::FlistStateStarted(_) => { | ||||||
|  |                 println!("FList creation started..."); | ||||||
|  |             } | ||||||
|  |             FlistState::FlistStateAccepted(_) => { | ||||||
|  |                 println!("FList creation request accepted..."); | ||||||
|  |             } | ||||||
|  |             _ => println!("State: {:?}", state), | ||||||
|  |         })), | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     // Wait for the FList to be created | ||||||
|  |     println!("Waiting for FList creation to complete..."); | ||||||
|  |  | ||||||
|  |     // Use ? operator to propagate errors properly | ||||||
|  |     let state = client | ||||||
|  |         .wait_for_flist_creation(&job_id, Some(options)) | ||||||
|  |         .await | ||||||
|  |         .map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?; | ||||||
|  |  | ||||||
|  |     println!("FList created successfully!"); | ||||||
|  |     println!("Final state: {:?}", state); | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								packages/clients/rfsclient/openapi.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/clients/rfsclient/openapi.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										3
									
								
								packages/clients/rfsclient/openapi/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								packages/clients/rfsclient/openapi/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | /target/ | ||||||
|  | **/*.rs.bk | ||||||
|  | Cargo.lock | ||||||
							
								
								
									
										23
									
								
								packages/clients/rfsclient/openapi/.openapi-generator-ignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								packages/clients/rfsclient/openapi/.openapi-generator-ignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | |||||||
|  | # OpenAPI Generator Ignore | ||||||
|  | # Generated by openapi-generator https://github.com/openapitools/openapi-generator | ||||||
|  |  | ||||||
|  | # Use this file to prevent files from being overwritten by the generator. | ||||||
|  | # The patterns follow closely to .gitignore or .dockerignore. | ||||||
|  |  | ||||||
|  | # As an example, the C# client generator defines ApiClient.cs. | ||||||
|  | # You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: | ||||||
|  | #ApiClient.cs | ||||||
|  |  | ||||||
|  | # You can match any string of characters against a directory, file or extension with a single asterisk (*): | ||||||
|  | #foo/*/qux | ||||||
|  | # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux | ||||||
|  |  | ||||||
|  | # You can recursively match patterns against a directory, file or extension with a double asterisk (**): | ||||||
|  | #foo/**/qux | ||||||
|  | # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux | ||||||
|  |  | ||||||
|  | # You can also negate patterns with an exclamation (!). | ||||||
|  | # For example, you can ignore all files in a docs folder with the file extension .md: | ||||||
|  | #docs/*.md | ||||||
|  | # Then explicitly reverse the ignore rule for a single file: | ||||||
|  | #!docs/README.md | ||||||
							
								
								
									
										125
									
								
								packages/clients/rfsclient/openapi/.openapi-generator/FILES
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								packages/clients/rfsclient/openapi/.openapi-generator/FILES
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | |||||||
|  | .gitignore | ||||||
|  | .travis.yml | ||||||
|  | Cargo.toml | ||||||
|  | README.md | ||||||
|  | docs/AuthenticationApi.md | ||||||
|  | docs/BlockDownloadsResponse.md | ||||||
|  | docs/BlockInfo.md | ||||||
|  | docs/BlockManagementApi.md | ||||||
|  | docs/BlockUploadedResponse.md | ||||||
|  | docs/BlocksResponse.md | ||||||
|  | docs/DirListTemplate.md | ||||||
|  | docs/DirLister.md | ||||||
|  | docs/ErrorTemplate.md | ||||||
|  | docs/FileDownloadRequest.md | ||||||
|  | docs/FileInfo.md | ||||||
|  | docs/FileManagementApi.md | ||||||
|  | docs/FileUploadResponse.md | ||||||
|  | docs/FlistBody.md | ||||||
|  | docs/FlistManagementApi.md | ||||||
|  | docs/FlistState.md | ||||||
|  | docs/FlistStateAccepted.md | ||||||
|  | docs/FlistStateCreated.md | ||||||
|  | docs/FlistStateInProgress.md | ||||||
|  | docs/FlistStateInfo.md | ||||||
|  | docs/FlistStateResponse.md | ||||||
|  | docs/FlistStateStarted.md | ||||||
|  | docs/HealthResponse.md | ||||||
|  | docs/Job.md | ||||||
|  | docs/ListBlocksParams.md | ||||||
|  | docs/ListBlocksResponse.md | ||||||
|  | docs/PreviewResponse.md | ||||||
|  | docs/ResponseError.md | ||||||
|  | docs/ResponseErrorBadRequest.md | ||||||
|  | docs/ResponseErrorConflict.md | ||||||
|  | docs/ResponseErrorForbidden.md | ||||||
|  | docs/ResponseErrorNotFound.md | ||||||
|  | docs/ResponseErrorTemplateError.md | ||||||
|  | docs/ResponseErrorUnauthorized.md | ||||||
|  | docs/ResponseResult.md | ||||||
|  | docs/ResponseResultBlockUploaded.md | ||||||
|  | docs/ResponseResultDirTemplate.md | ||||||
|  | docs/ResponseResultFileUploaded.md | ||||||
|  | docs/ResponseResultFlistCreated.md | ||||||
|  | docs/ResponseResultFlistState.md | ||||||
|  | docs/ResponseResultFlists.md | ||||||
|  | docs/ResponseResultPreviewFlist.md | ||||||
|  | docs/ResponseResultRes.md | ||||||
|  | docs/ResponseResultSignedIn.md | ||||||
|  | docs/SignInBody.md | ||||||
|  | docs/SignInResponse.md | ||||||
|  | docs/SystemApi.md | ||||||
|  | docs/TemplateErr.md | ||||||
|  | docs/TemplateErrBadRequest.md | ||||||
|  | docs/TemplateErrInternalServerError.md | ||||||
|  | docs/TemplateErrNotFound.md | ||||||
|  | docs/UploadBlockParams.md | ||||||
|  | docs/UserBlockInfo.md | ||||||
|  | docs/UserBlocksResponse.md | ||||||
|  | docs/VerifyBlock.md | ||||||
|  | docs/VerifyBlocksRequest.md | ||||||
|  | docs/VerifyBlocksResponse.md | ||||||
|  | docs/WebsiteServingApi.md | ||||||
|  | git_push.sh | ||||||
|  | src/apis/authentication_api.rs | ||||||
|  | src/apis/block_management_api.rs | ||||||
|  | src/apis/configuration.rs | ||||||
|  | src/apis/file_management_api.rs | ||||||
|  | src/apis/flist_management_api.rs | ||||||
|  | src/apis/mod.rs | ||||||
|  | src/apis/system_api.rs | ||||||
|  | src/apis/website_serving_api.rs | ||||||
|  | src/lib.rs | ||||||
|  | src/models/block_downloads_response.rs | ||||||
|  | src/models/block_info.rs | ||||||
|  | src/models/block_uploaded_response.rs | ||||||
|  | src/models/blocks_response.rs | ||||||
|  | src/models/dir_list_template.rs | ||||||
|  | src/models/dir_lister.rs | ||||||
|  | src/models/error_template.rs | ||||||
|  | src/models/file_download_request.rs | ||||||
|  | src/models/file_info.rs | ||||||
|  | src/models/file_upload_response.rs | ||||||
|  | src/models/flist_body.rs | ||||||
|  | src/models/flist_state.rs | ||||||
|  | src/models/flist_state_accepted.rs | ||||||
|  | src/models/flist_state_created.rs | ||||||
|  | src/models/flist_state_in_progress.rs | ||||||
|  | src/models/flist_state_info.rs | ||||||
|  | src/models/flist_state_response.rs | ||||||
|  | src/models/flist_state_started.rs | ||||||
|  | src/models/health_response.rs | ||||||
|  | src/models/job.rs | ||||||
|  | src/models/list_blocks_params.rs | ||||||
|  | src/models/list_blocks_response.rs | ||||||
|  | src/models/mod.rs | ||||||
|  | src/models/preview_response.rs | ||||||
|  | src/models/response_error.rs | ||||||
|  | src/models/response_error_bad_request.rs | ||||||
|  | src/models/response_error_conflict.rs | ||||||
|  | src/models/response_error_forbidden.rs | ||||||
|  | src/models/response_error_not_found.rs | ||||||
|  | src/models/response_error_template_error.rs | ||||||
|  | src/models/response_error_unauthorized.rs | ||||||
|  | src/models/response_result.rs | ||||||
|  | src/models/response_result_block_uploaded.rs | ||||||
|  | src/models/response_result_dir_template.rs | ||||||
|  | src/models/response_result_file_uploaded.rs | ||||||
|  | src/models/response_result_flist_created.rs | ||||||
|  | src/models/response_result_flist_state.rs | ||||||
|  | src/models/response_result_flists.rs | ||||||
|  | src/models/response_result_preview_flist.rs | ||||||
|  | src/models/response_result_res.rs | ||||||
|  | src/models/response_result_signed_in.rs | ||||||
|  | src/models/sign_in_body.rs | ||||||
|  | src/models/sign_in_response.rs | ||||||
|  | src/models/template_err.rs | ||||||
|  | src/models/template_err_bad_request.rs | ||||||
|  | src/models/template_err_internal_server_error.rs | ||||||
|  | src/models/template_err_not_found.rs | ||||||
|  | src/models/upload_block_params.rs | ||||||
|  | src/models/user_block_info.rs | ||||||
|  | src/models/user_blocks_response.rs | ||||||
|  | src/models/verify_block.rs | ||||||
|  | src/models/verify_blocks_request.rs | ||||||
|  | src/models/verify_blocks_response.rs | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | 7.13.0 | ||||||
							
								
								
									
										1
									
								
								packages/clients/rfsclient/openapi/.travis.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								packages/clients/rfsclient/openapi/.travis.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | language: rust | ||||||
							
								
								
									
										15
									
								
								packages/clients/rfsclient/openapi/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								packages/clients/rfsclient/openapi/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | [package] | ||||||
|  | name = "openapi" | ||||||
|  | version = "0.2.0" | ||||||
|  | authors = ["OpenAPI Generator team and contributors"] | ||||||
|  | description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)" | ||||||
|  | license = "" | ||||||
|  | edition = "2021" | ||||||
|  |  | ||||||
|  | [dependencies] | ||||||
|  | serde = { version = "^1.0", features = ["derive"] } | ||||||
|  | serde_with = { version = "^3.8", default-features = false, features = ["base64", "std", "macros"] } | ||||||
|  | serde_json = "^1.0" | ||||||
|  | serde_repr = "^0.1" | ||||||
|  | url = "^2.5" | ||||||
|  | reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] } | ||||||
							
								
								
									
										114
									
								
								packages/clients/rfsclient/openapi/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								packages/clients/rfsclient/openapi/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | |||||||
|  | # Rust API client for openapi | ||||||
|  |  | ||||||
|  | No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Overview | ||||||
|  |  | ||||||
|  | This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project.  By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client. | ||||||
|  |  | ||||||
|  | - API version: 0.2.0 | ||||||
|  | - Package version: 0.2.0 | ||||||
|  | - Generator version: 7.13.0 | ||||||
|  | - Build package: `org.openapitools.codegen.languages.RustClientCodegen` | ||||||
|  |  | ||||||
|  | ## Installation | ||||||
|  |  | ||||||
|  | Put the package under your project folder in a directory named `openapi` and add the following to `Cargo.toml` under `[dependencies]`: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | openapi = { path = "./openapi" } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Documentation for API Endpoints | ||||||
|  |  | ||||||
|  | All URIs are relative to *http://localhost* | ||||||
|  |  | ||||||
|  | Class | Method | HTTP request | Description | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | *AuthenticationApi* | [**sign_in_handler**](docs/AuthenticationApi.md#sign_in_handler) | **POST** /api/v1/signin |  | ||||||
|  | *BlockManagementApi* | [**check_block_handler**](docs/BlockManagementApi.md#check_block_handler) | **HEAD** /api/v1/block/{hash} | Checks a block by its hash. | ||||||
|  | *BlockManagementApi* | [**get_block_downloads_handler**](docs/BlockManagementApi.md#get_block_downloads_handler) | **GET** /api/v1/block/{hash}/downloads | Retrieve the number of times a block has been downloaded. | ||||||
|  | *BlockManagementApi* | [**get_block_handler**](docs/BlockManagementApi.md#get_block_handler) | **GET** /api/v1/block/{hash} | Retrieve a block by its hash. | ||||||
|  | *BlockManagementApi* | [**get_blocks_by_hash_handler**](docs/BlockManagementApi.md#get_blocks_by_hash_handler) | **GET** /api/v1/blocks/{hash} | Retrieve blocks by hash (file hash or block hash). | ||||||
|  | *BlockManagementApi* | [**get_user_blocks_handler**](docs/BlockManagementApi.md#get_user_blocks_handler) | **GET** /api/v1/user/blocks | Retrieve all blocks uploaded by a specific user. | ||||||
|  | *BlockManagementApi* | [**list_blocks_handler**](docs/BlockManagementApi.md#list_blocks_handler) | **GET** /api/v1/blocks | List all block hashes in the server with pagination | ||||||
|  | *BlockManagementApi* | [**upload_block_handler**](docs/BlockManagementApi.md#upload_block_handler) | **POST** /api/v1/block | Upload a block to the server. | ||||||
|  | *BlockManagementApi* | [**verify_blocks_handler**](docs/BlockManagementApi.md#verify_blocks_handler) | **POST** /api/v1/block/verify | Verify if multiple blocks exist on the server. | ||||||
|  | *FileManagementApi* | [**get_file_handler**](docs/FileManagementApi.md#get_file_handler) | **GET** /api/v1/file/{hash} | Retrieve a file by its hash from path, with optional custom filename in request body. | ||||||
|  | *FileManagementApi* | [**upload_file_handler**](docs/FileManagementApi.md#upload_file_handler) | **POST** /api/v1/file | Upload a file to the server. | ||||||
|  | *FlistManagementApi* | [**create_flist_handler**](docs/FlistManagementApi.md#create_flist_handler) | **POST** /api/v1/fl |  | ||||||
|  | *FlistManagementApi* | [**get_flist_state_handler**](docs/FlistManagementApi.md#get_flist_state_handler) | **GET** /api/v1/fl/{job_id} |  | ||||||
|  | *FlistManagementApi* | [**list_flists_handler**](docs/FlistManagementApi.md#list_flists_handler) | **GET** /api/v1/fl |  | ||||||
|  | *FlistManagementApi* | [**preview_flist_handler**](docs/FlistManagementApi.md#preview_flist_handler) | **GET** /api/v1/fl/preview/{flist_path} |  | ||||||
|  | *FlistManagementApi* | [**serve_flists**](docs/FlistManagementApi.md#serve_flists) | **GET** /{path} | Serve flist files from the server's filesystem | ||||||
|  | *SystemApi* | [**health_check_handler**](docs/SystemApi.md#health_check_handler) | **GET** /api/v1 |  | ||||||
|  | *WebsiteServingApi* | [**serve_website_handler**](docs/WebsiteServingApi.md#serve_website_handler) | **GET** /api/v1/website/{website_hash}/{path} |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Documentation For Models | ||||||
|  |  | ||||||
|  |  - [BlockDownloadsResponse](docs/BlockDownloadsResponse.md) | ||||||
|  |  - [BlockInfo](docs/BlockInfo.md) | ||||||
|  |  - [BlockUploadedResponse](docs/BlockUploadedResponse.md) | ||||||
|  |  - [BlocksResponse](docs/BlocksResponse.md) | ||||||
|  |  - [DirListTemplate](docs/DirListTemplate.md) | ||||||
|  |  - [DirLister](docs/DirLister.md) | ||||||
|  |  - [ErrorTemplate](docs/ErrorTemplate.md) | ||||||
|  |  - [FileDownloadRequest](docs/FileDownloadRequest.md) | ||||||
|  |  - [FileInfo](docs/FileInfo.md) | ||||||
|  |  - [FileUploadResponse](docs/FileUploadResponse.md) | ||||||
|  |  - [FlistBody](docs/FlistBody.md) | ||||||
|  |  - [FlistState](docs/FlistState.md) | ||||||
|  |  - [FlistStateAccepted](docs/FlistStateAccepted.md) | ||||||
|  |  - [FlistStateCreated](docs/FlistStateCreated.md) | ||||||
|  |  - [FlistStateInProgress](docs/FlistStateInProgress.md) | ||||||
|  |  - [FlistStateInfo](docs/FlistStateInfo.md) | ||||||
|  |  - [FlistStateResponse](docs/FlistStateResponse.md) | ||||||
|  |  - [FlistStateStarted](docs/FlistStateStarted.md) | ||||||
|  |  - [HealthResponse](docs/HealthResponse.md) | ||||||
|  |  - [Job](docs/Job.md) | ||||||
|  |  - [ListBlocksParams](docs/ListBlocksParams.md) | ||||||
|  |  - [ListBlocksResponse](docs/ListBlocksResponse.md) | ||||||
|  |  - [PreviewResponse](docs/PreviewResponse.md) | ||||||
|  |  - [ResponseError](docs/ResponseError.md) | ||||||
|  |  - [ResponseErrorBadRequest](docs/ResponseErrorBadRequest.md) | ||||||
|  |  - [ResponseErrorConflict](docs/ResponseErrorConflict.md) | ||||||
|  |  - [ResponseErrorForbidden](docs/ResponseErrorForbidden.md) | ||||||
|  |  - [ResponseErrorNotFound](docs/ResponseErrorNotFound.md) | ||||||
|  |  - [ResponseErrorTemplateError](docs/ResponseErrorTemplateError.md) | ||||||
|  |  - [ResponseErrorUnauthorized](docs/ResponseErrorUnauthorized.md) | ||||||
|  |  - [ResponseResult](docs/ResponseResult.md) | ||||||
|  |  - [ResponseResultBlockUploaded](docs/ResponseResultBlockUploaded.md) | ||||||
|  |  - [ResponseResultDirTemplate](docs/ResponseResultDirTemplate.md) | ||||||
|  |  - [ResponseResultFileUploaded](docs/ResponseResultFileUploaded.md) | ||||||
|  |  - [ResponseResultFlistCreated](docs/ResponseResultFlistCreated.md) | ||||||
|  |  - [ResponseResultFlistState](docs/ResponseResultFlistState.md) | ||||||
|  |  - [ResponseResultFlists](docs/ResponseResultFlists.md) | ||||||
|  |  - [ResponseResultPreviewFlist](docs/ResponseResultPreviewFlist.md) | ||||||
|  |  - [ResponseResultRes](docs/ResponseResultRes.md) | ||||||
|  |  - [ResponseResultSignedIn](docs/ResponseResultSignedIn.md) | ||||||
|  |  - [SignInBody](docs/SignInBody.md) | ||||||
|  |  - [SignInResponse](docs/SignInResponse.md) | ||||||
|  |  - [TemplateErr](docs/TemplateErr.md) | ||||||
|  |  - [TemplateErrBadRequest](docs/TemplateErrBadRequest.md) | ||||||
|  |  - [TemplateErrInternalServerError](docs/TemplateErrInternalServerError.md) | ||||||
|  |  - [TemplateErrNotFound](docs/TemplateErrNotFound.md) | ||||||
|  |  - [UploadBlockParams](docs/UploadBlockParams.md) | ||||||
|  |  - [UserBlockInfo](docs/UserBlockInfo.md) | ||||||
|  |  - [UserBlocksResponse](docs/UserBlocksResponse.md) | ||||||
|  |  - [VerifyBlock](docs/VerifyBlock.md) | ||||||
|  |  - [VerifyBlocksRequest](docs/VerifyBlocksRequest.md) | ||||||
|  |  - [VerifyBlocksResponse](docs/VerifyBlocksResponse.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | To get access to the crate's generated documentation, use: | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | cargo doc --open | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Author | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										37
									
								
								packages/clients/rfsclient/openapi/docs/AuthenticationApi.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								packages/clients/rfsclient/openapi/docs/AuthenticationApi.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | # \AuthenticationApi | ||||||
|  |  | ||||||
|  | All URIs are relative to *http://localhost* | ||||||
|  |  | ||||||
|  | Method | HTTP request | Description | ||||||
|  | ------------- | ------------- | ------------- | ||||||
|  | [**sign_in_handler**](AuthenticationApi.md#sign_in_handler) | **POST** /api/v1/signin |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## sign_in_handler | ||||||
|  |  | ||||||
|  | > models::SignInResponse sign_in_handler(sign_in_body) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **sign_in_body** | [**SignInBody**](SignInBody.md) |  | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::SignInResponse**](SignInResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: application/json | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								packages/clients/rfsclient/openapi/docs/Block.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								packages/clients/rfsclient/openapi/docs/Block.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | # Block | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **data** | [**std::path::PathBuf**](std::path::PathBuf.md) |  |  | ||||||
|  | **hash** | **String** |  |  | ||||||
|  | **index** | **i64** |  |  | ||||||
|  | **size** | **i32** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | # BlockDownloadsResponse | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **block_hash** | **String** | Block hash |  | ||||||
|  | **block_size** | **i64** | Size of the block in bytes |  | ||||||
|  | **downloads_count** | **i64** | Number of times the block has been downloaded |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/BlockInfo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/BlockInfo.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # BlockInfo | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **hash** | **String** | Block hash |  | ||||||
|  | **index** | **i64** | Block index within the file |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										250
									
								
								packages/clients/rfsclient/openapi/docs/BlockManagementApi.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										250
									
								
								packages/clients/rfsclient/openapi/docs/BlockManagementApi.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,250 @@ | |||||||
|  | # \BlockManagementApi | ||||||
|  |  | ||||||
|  | All URIs are relative to *http://localhost* | ||||||
|  |  | ||||||
|  | Method | HTTP request | Description | ||||||
|  | ------------- | ------------- | ------------- | ||||||
|  | [**check_block_handler**](BlockManagementApi.md#check_block_handler) | **HEAD** /api/v1/block/{hash} | Checks a block by its hash. | ||||||
|  | [**get_block_downloads_handler**](BlockManagementApi.md#get_block_downloads_handler) | **GET** /api/v1/block/{hash}/downloads | Retrieve the number of times a block has been downloaded. | ||||||
|  | [**get_block_handler**](BlockManagementApi.md#get_block_handler) | **GET** /api/v1/block/{hash} | Retrieve a block by its hash. | ||||||
|  | [**get_blocks_by_hash_handler**](BlockManagementApi.md#get_blocks_by_hash_handler) | **GET** /api/v1/blocks/{hash} | Retrieve blocks by hash (file hash or block hash). | ||||||
|  | [**get_user_blocks_handler**](BlockManagementApi.md#get_user_blocks_handler) | **GET** /api/v1/user/blocks | Retrieve all blocks uploaded by a specific user. | ||||||
|  | [**list_blocks_handler**](BlockManagementApi.md#list_blocks_handler) | **GET** /api/v1/blocks | List all block hashes in the server with pagination | ||||||
|  | [**upload_block_handler**](BlockManagementApi.md#upload_block_handler) | **POST** /api/v1/block | Upload a block to the server. | ||||||
|  | [**verify_blocks_handler**](BlockManagementApi.md#verify_blocks_handler) | **POST** /api/v1/block/verify | Verify if multiple blocks exist on the server. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## check_block_handler | ||||||
|  |  | ||||||
|  | > check_block_handler(hash) | ||||||
|  | Checks a block by its hash. | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **hash** | **String** | Block hash | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  |  (empty response body) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## get_block_downloads_handler | ||||||
|  |  | ||||||
|  | > models::BlockDownloadsResponse get_block_downloads_handler(hash) | ||||||
|  | Retrieve the number of times a block has been downloaded. | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **hash** | **String** | Block hash | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::BlockDownloadsResponse**](BlockDownloadsResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## get_block_handler | ||||||
|  |  | ||||||
|  | > std::path::PathBuf get_block_handler(hash) | ||||||
|  | Retrieve a block by its hash. | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **hash** | **String** | Block hash | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**std::path::PathBuf**](std::path::PathBuf.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/octet-stream, application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## get_blocks_by_hash_handler | ||||||
|  |  | ||||||
|  | > models::BlocksResponse get_blocks_by_hash_handler(hash) | ||||||
|  | Retrieve blocks by hash (file hash or block hash). | ||||||
|  |  | ||||||
|  | If the hash is a file hash, returns all blocks with their block index related to that file. If the hash is a block hash, returns the block itself. | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **hash** | **String** | File hash or block hash | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::BlocksResponse**](BlocksResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## get_user_blocks_handler | ||||||
|  |  | ||||||
|  | > models::UserBlocksResponse get_user_blocks_handler(page, per_page) | ||||||
|  | Retrieve all blocks uploaded by a specific user. | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **page** | Option<**i32**> | Page number (1-indexed) |  | | ||||||
|  | **per_page** | Option<**i32**> | Number of items per page |  | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::UserBlocksResponse**](UserBlocksResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | [bearerAuth](../README.md#bearerAuth) | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## list_blocks_handler | ||||||
|  |  | ||||||
|  | > models::ListBlocksResponse list_blocks_handler(page, per_page) | ||||||
|  | List all block hashes in the server with pagination | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **page** | Option<**i32**> | Page number (1-indexed) |  | | ||||||
|  | **per_page** | Option<**i32**> | Number of items per page |  | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::ListBlocksResponse**](ListBlocksResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## upload_block_handler | ||||||
|  |  | ||||||
|  | > models::BlockUploadedResponse upload_block_handler(file_hash, idx, body) | ||||||
|  | Upload a block to the server. | ||||||
|  |  | ||||||
|  | If the block already exists, the server will return a 200 OK response. If the block is new, the server will return a 201 Created response. | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **file_hash** | **String** | File hash associated with the block | [required] | | ||||||
|  | **idx** | **i64** | Block index within the file | [required] | | ||||||
|  | **body** | **std::path::PathBuf** | Block data to upload | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::BlockUploadedResponse**](BlockUploadedResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | [bearerAuth](../README.md#bearerAuth) | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: application/octet-stream | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## verify_blocks_handler | ||||||
|  |  | ||||||
|  | > models::VerifyBlocksResponse verify_blocks_handler(verify_blocks_request) | ||||||
|  | Verify if multiple blocks exist on the server. | ||||||
|  |  | ||||||
|  | Returns a list of missing blocks. | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **verify_blocks_request** | [**VerifyBlocksRequest**](VerifyBlocksRequest.md) | List of block hashes to verify | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::VerifyBlocksResponse**](VerifyBlocksResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: application/json | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | # BlockUploadedResponse | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **hash** | **String** |  |  | ||||||
|  | **message** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/BlocksResponse.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/BlocksResponse.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # BlocksResponse | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **blocks** | [**Vec<models::BlockInfo>**](BlockInfo.md) | List of blocks with their indices |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/DirListTemplate.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/DirListTemplate.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # DirListTemplate | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **cur_path** | **String** |  |  | ||||||
|  | **lister** | [**models::DirLister**](DirLister.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/DirLister.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/DirLister.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # DirLister | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **files** | [**Vec<models::FileInfo>**](FileInfo.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								packages/clients/rfsclient/openapi/docs/ErrorTemplate.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/clients/rfsclient/openapi/docs/ErrorTemplate.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | # ErrorTemplate | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **cur_path** | **String** |  |  | ||||||
|  | **err** | [**models::TemplateErr**](TemplateErr.md) |  |  | ||||||
|  | **message** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/File.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/File.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # File | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **file_content** | [**std::path::PathBuf**](std::path::PathBuf.md) |  |  | ||||||
|  | **file_hash** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # FileDownloadRequest | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **file_name** | **String** | The custom filename to use for download |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										16
									
								
								packages/clients/rfsclient/openapi/docs/FileInfo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								packages/clients/rfsclient/openapi/docs/FileInfo.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | # FileInfo | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **is_file** | **bool** |  |  | ||||||
|  | **last_modified** | **i64** |  |  | ||||||
|  | **name** | **String** |  |  | ||||||
|  | **path_uri** | **String** |  |  | ||||||
|  | **progress** | **f32** |  |  | ||||||
|  | **size** | **i64** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										71
									
								
								packages/clients/rfsclient/openapi/docs/FileManagementApi.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								packages/clients/rfsclient/openapi/docs/FileManagementApi.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | # \FileManagementApi | ||||||
|  |  | ||||||
|  | All URIs are relative to *http://localhost* | ||||||
|  |  | ||||||
|  | Method | HTTP request | Description | ||||||
|  | ------------- | ------------- | ------------- | ||||||
|  | [**get_file_handler**](FileManagementApi.md#get_file_handler) | **GET** /api/v1/file/{hash} | Retrieve a file by its hash from path, with optional custom filename in request body. | ||||||
|  | [**upload_file_handler**](FileManagementApi.md#upload_file_handler) | **POST** /api/v1/file | Upload a file to the server. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## get_file_handler | ||||||
|  |  | ||||||
|  | > std::path::PathBuf get_file_handler(hash, file_download_request) | ||||||
|  | Retrieve a file by its hash from path, with optional custom filename in request body. | ||||||
|  |  | ||||||
|  | The file will be reconstructed from its blocks. | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **hash** | **String** | File hash | [required] | | ||||||
|  | **file_download_request** | [**FileDownloadRequest**](FileDownloadRequest.md) | Optional custom filename for download | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**std::path::PathBuf**](std::path::PathBuf.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: application/json | ||||||
|  | - **Accept**: application/octet-stream, application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## upload_file_handler | ||||||
|  |  | ||||||
|  | > models::FileUploadResponse upload_file_handler(body) | ||||||
|  | Upload a file to the server. | ||||||
|  |  | ||||||
|  | The file will be split into blocks and stored in the database. | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **body** | **std::path::PathBuf** | File data to upload | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::FileUploadResponse**](FileUploadResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | [bearerAuth](../README.md#bearerAuth) | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: application/octet-stream | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | # FileUploadResponse | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **file_hash** | **String** | The file hash |  | ||||||
|  | **message** | **String** | Message indicating success |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								packages/clients/rfsclient/openapi/docs/FlistBody.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								packages/clients/rfsclient/openapi/docs/FlistBody.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # FlistBody | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **auth** | Option<**String**> |  | [optional] | ||||||
|  | **email** | Option<**String**> |  | [optional] | ||||||
|  | **identity_token** | Option<**String**> |  | [optional] | ||||||
|  | **image_name** | **String** |  |  | ||||||
|  | **password** | Option<**String**> |  | [optional] | ||||||
|  | **registry_token** | Option<**String**> |  | [optional] | ||||||
|  | **server_address** | Option<**String**> |  | [optional] | ||||||
|  | **username** | Option<**String**> |  | [optional] | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										150
									
								
								packages/clients/rfsclient/openapi/docs/FlistManagementApi.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								packages/clients/rfsclient/openapi/docs/FlistManagementApi.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | |||||||
|  | # \FlistManagementApi | ||||||
|  |  | ||||||
|  | All URIs are relative to *http://localhost* | ||||||
|  |  | ||||||
|  | Method | HTTP request | Description | ||||||
|  | ------------- | ------------- | ------------- | ||||||
|  | [**create_flist_handler**](FlistManagementApi.md#create_flist_handler) | **POST** /api/v1/fl |  | ||||||
|  | [**get_flist_state_handler**](FlistManagementApi.md#get_flist_state_handler) | **GET** /api/v1/fl/{job_id} |  | ||||||
|  | [**list_flists_handler**](FlistManagementApi.md#list_flists_handler) | **GET** /api/v1/fl |  | ||||||
|  | [**preview_flist_handler**](FlistManagementApi.md#preview_flist_handler) | **GET** /api/v1/fl/preview/{flist_path} |  | ||||||
|  | [**serve_flists**](FlistManagementApi.md#serve_flists) | **GET** /{path} | Serve flist files from the server's filesystem | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## create_flist_handler | ||||||
|  |  | ||||||
|  | > models::Job create_flist_handler(flist_body) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **flist_body** | [**FlistBody**](FlistBody.md) |  | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::Job**](Job.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | [bearerAuth](../README.md#bearerAuth) | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: application/json | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## get_flist_state_handler | ||||||
|  |  | ||||||
|  | > models::FlistStateResponse get_flist_state_handler(job_id) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **job_id** | **String** | flist job id | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::FlistStateResponse**](FlistStateResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | [bearerAuth](../README.md#bearerAuth) | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## list_flists_handler | ||||||
|  |  | ||||||
|  | > std::collections::HashMap<String, Vec<models::FileInfo>> list_flists_handler() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  | This endpoint does not need any parameter. | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**std::collections::HashMap<String, Vec<models::FileInfo>>**](Vec.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## preview_flist_handler | ||||||
|  |  | ||||||
|  | > models::PreviewResponse preview_flist_handler(flist_path) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **flist_path** | **String** | flist file path | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::PreviewResponse**](PreviewResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## serve_flists | ||||||
|  |  | ||||||
|  | > std::path::PathBuf serve_flists(path) | ||||||
|  | Serve flist files from the server's filesystem | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **path** | **String** | Path to the flist file or directory to serve | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**std::path::PathBuf**](std::path::PathBuf.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/octet-stream, application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
							
								
								
									
										37
									
								
								packages/clients/rfsclient/openapi/docs/FlistServingApi.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								packages/clients/rfsclient/openapi/docs/FlistServingApi.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | |||||||
|  | # \FlistServingApi | ||||||
|  |  | ||||||
|  | All URIs are relative to *http://localhost* | ||||||
|  |  | ||||||
|  | Method | HTTP request | Description | ||||||
|  | ------------- | ------------- | ------------- | ||||||
|  | [**serve_flists**](FlistServingApi.md#serve_flists) | **GET** /{path} | Serve flist files from the server's filesystem | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## serve_flists | ||||||
|  |  | ||||||
|  | > models::ResponseResult serve_flists(path) | ||||||
|  | Serve flist files from the server's filesystem | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  |  | ||||||
|  | Name | Type | Description  | Required | Notes | ||||||
|  | ------------- | ------------- | ------------- | ------------- | ------------- | ||||||
|  | **path** | **String** | Path to the flist file or directory to serve | [required] | | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::ResponseResult**](ResponseResult.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								packages/clients/rfsclient/openapi/docs/FlistState.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								packages/clients/rfsclient/openapi/docs/FlistState.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | # FlistState | ||||||
|  |  | ||||||
|  | ## Enum Variants | ||||||
|  |  | ||||||
|  | | Name | Description | | ||||||
|  | |---- | -----| | ||||||
|  | | FlistStateAccepted |  | | ||||||
|  | | FlistStateCreated |  | | ||||||
|  | | FlistStateInProgress |  | | ||||||
|  | | FlistStateStarted |  | | ||||||
|  | | String |  | | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # FlistStateAccepted | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **accepted** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/FlistStateCreated.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/FlistStateCreated.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # FlistStateCreated | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **created** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # FlistStateInProgress | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **in_progress** | [**models::FlistStateInfo**](FlistStateInfo.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/FlistStateInfo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/FlistStateInfo.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # FlistStateInfo | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **msg** | **String** |  |  | ||||||
|  | **progress** | **f32** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # FlistStateResponse | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **flist_state** | [**models::FlistState**](FlistState.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/FlistStateStarted.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/FlistStateStarted.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # FlistStateStarted | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **started** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/HealthResponse.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/HealthResponse.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # HealthResponse | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **msg** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/Job.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/Job.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # Job | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **id** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/ListBlocksParams.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/ListBlocksParams.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # ListBlocksParams | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **page** | Option<**i32**> | Page number (1-indexed) | [optional][default to 1] | ||||||
|  | **per_page** | Option<**i32**> | Number of items per page | [optional][default to 50] | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | # ListBlocksResponse | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **blocks** | **Vec<String>** | List of block hashes |  | ||||||
|  | **page** | **i32** | Current page number |  | ||||||
|  | **per_page** | **i32** | Number of items per page |  | ||||||
|  | **total** | **i64** | Total number of blocks |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								packages/clients/rfsclient/openapi/docs/PreviewResponse.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/clients/rfsclient/openapi/docs/PreviewResponse.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | # PreviewResponse | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **checksum** | **String** |  |  | ||||||
|  | **content** | **Vec<String>** |  |  | ||||||
|  | **metadata** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								packages/clients/rfsclient/openapi/docs/ResponseError.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/clients/rfsclient/openapi/docs/ResponseError.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | # ResponseError | ||||||
|  |  | ||||||
|  | ## Enum Variants | ||||||
|  |  | ||||||
|  | | Name | Description | | ||||||
|  | |---- | -----| | ||||||
|  | | ResponseErrorBadRequest |  | | ||||||
|  | | ResponseErrorConflict |  | | ||||||
|  | | ResponseErrorForbidden |  | | ||||||
|  | | ResponseErrorNotFound |  | | ||||||
|  | | ResponseErrorTemplateError |  | | ||||||
|  | | ResponseErrorUnauthorized |  | | ||||||
|  | | String |  | | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseErrorBadRequest | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **bad_request** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseErrorConflict | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **conflict** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseErrorForbidden | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **forbidden** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseErrorNotFound | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **not_found** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseErrorTemplateError | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **template_error** | [**models::ErrorTemplate**](ErrorTemplate.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseErrorUnauthorized | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **unauthorized** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								packages/clients/rfsclient/openapi/docs/ResponseResult.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								packages/clients/rfsclient/openapi/docs/ResponseResult.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  | # ResponseResult | ||||||
|  |  | ||||||
|  | ## Enum Variants | ||||||
|  |  | ||||||
|  | | Name | Description | | ||||||
|  | |---- | -----| | ||||||
|  | | ResponseResultBlockUploaded |  | | ||||||
|  | | ResponseResultDirTemplate |  | | ||||||
|  | | ResponseResultFileUploaded |  | | ||||||
|  | | ResponseResultFlistCreated |  | | ||||||
|  | | ResponseResultFlistState |  | | ||||||
|  | | ResponseResultFlists |  | | ||||||
|  | | ResponseResultPreviewFlist |  | | ||||||
|  | | ResponseResultRes |  | | ||||||
|  | | ResponseResultSignedIn |  | | ||||||
|  | | String |  | | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseResultBlockUploaded | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **block_uploaded** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseResultDirTemplate | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **dir_template** | [**models::DirListTemplate**](DirListTemplate.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseResultFileUploaded | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **file_uploaded** | [**models::FileUploadResponse**](FileUploadResponse.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseResultFlistCreated | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **flist_created** | [**models::Job**](Job.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseResultFlistState | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **flist_state** | [**models::FlistState**](FlistState.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseResultFlists | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **flists** | [**std::collections::HashMap<String, Vec<models::FileInfo>>**](Vec.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseResultPreviewFlist | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **preview_flist** | [**models::PreviewResponse**](PreviewResponse.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/ResponseResultRes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/ResponseResultRes.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseResultRes | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **res** | [**std::path::PathBuf**](std::path::PathBuf.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # ResponseResultSignedIn | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **signed_in** | [**models::SignInResponse**](SignInResponse.md) |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/SignInBody.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/SignInBody.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # SignInBody | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **password** | **String** |  |  | ||||||
|  | **username** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/SignInResponse.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								packages/clients/rfsclient/openapi/docs/SignInResponse.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | # SignInResponse | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **access_token** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										34
									
								
								packages/clients/rfsclient/openapi/docs/SystemApi.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								packages/clients/rfsclient/openapi/docs/SystemApi.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | # \SystemApi | ||||||
|  |  | ||||||
|  | All URIs are relative to *http://localhost* | ||||||
|  |  | ||||||
|  | Method | HTTP request | Description | ||||||
|  | ------------- | ------------- | ------------- | ||||||
|  | [**health_check_handler**](SystemApi.md#health_check_handler) | **GET** /api/v1 |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## health_check_handler | ||||||
|  |  | ||||||
|  | > models::HealthResponse health_check_handler() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ### Parameters | ||||||
|  |  | ||||||
|  | This endpoint does not need any parameter. | ||||||
|  |  | ||||||
|  | ### Return type | ||||||
|  |  | ||||||
|  | [**models::HealthResponse**](HealthResponse.md) | ||||||
|  |  | ||||||
|  | ### Authorization | ||||||
|  |  | ||||||
|  | No authorization required | ||||||
|  |  | ||||||
|  | ### HTTP request headers | ||||||
|  |  | ||||||
|  | - **Content-Type**: Not defined | ||||||
|  | - **Accept**: application/json | ||||||
|  |  | ||||||
|  | [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								packages/clients/rfsclient/openapi/docs/TemplateErr.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								packages/clients/rfsclient/openapi/docs/TemplateErr.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | # TemplateErr | ||||||
|  |  | ||||||
|  | ## Enum Variants | ||||||
|  |  | ||||||
|  | | Name | Description | | ||||||
|  | |---- | -----| | ||||||
|  | | TemplateErrBadRequest |  | | ||||||
|  | | TemplateErrInternalServerError |  | | ||||||
|  | | TemplateErrNotFound |  | | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # TemplateErrBadRequest | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **bad_request** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # TemplateErrInternalServerError | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **internal_server_error** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -0,0 +1,11 @@ | |||||||
|  | # TemplateErrNotFound | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **not_found** | **String** |  |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/UploadBlockParams.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/UploadBlockParams.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # UploadBlockParams | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **file_hash** | **String** | File hash associated with the block |  | ||||||
|  | **idx** | **i64** | Block index within the file |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/UserBlockInfo.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								packages/clients/rfsclient/openapi/docs/UserBlockInfo.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | |||||||
|  | # UserBlockInfo | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | Name | Type | Description | Notes | ||||||
|  | ------------ | ------------- | ------------- | ------------- | ||||||
|  | **hash** | **String** | Block hash |  | ||||||
|  | **size** | **i64** | Block size in bytes |  | ||||||
|  |  | ||||||
|  | [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||||
|  |  | ||||||
|  |  | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user