use crate::lib::error::{AptOstreeError, AptOstreeResult}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; use uuid::Uuid; /// Transaction types for different operations #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum TransactionType { PkgChange, // Package installation/removal Deploy, // Deployment operations Rebase, // System rebase operations Upgrade, // System upgrade operations Rollback, // Rollback operations Kargs, // Kernel argument changes Initramfs, // Initramfs modifications Override, // Package override changes UsrOverlay, // User overlay operations ApplyLive, // Live deployment changes Finalize, // Deployment finalization Cleanup, // Cleanup operations Reload, // Configuration reload Reset, // Reset operations RefreshMd, // Metadata refresh Compose, // Tree composition Container, // Container operations Experimental, // Experimental features } /// Upgrade-specific transaction data #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpgradeTransaction { pub packages_to_install: Vec, pub packages_to_remove: Vec, pub allow_downgrade: bool, pub require_signatures: bool, pub reboot_after: bool, pub preview_mode: bool, pub cache_only: bool, pub download_only: bool, } impl Default for UpgradeTransaction { fn default() -> Self { Self { packages_to_install: Vec::new(), packages_to_remove: Vec::new(), allow_downgrade: false, require_signatures: true, reboot_after: false, preview_mode: false, cache_only: false, download_only: false, } } } /// Transaction states throughout the lifecycle #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)] pub enum TransactionState { Initialized, // Transaction created Preparing, // Preparation phase Ready, // Ready for execution Running, // Currently executing Paused, // Paused for user input Completed, // Successfully completed Failed, // Execution failed Cancelled, // User cancelled RollingBack, // Rolling back changes RolledBack, // Successfully rolled back } /// Transaction step information for detailed progress tracking #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TransactionStep { pub id: String, pub name: String, pub description: String, pub status: StepStatus, pub progress: f64, pub started_at: Option>, pub completed_at: Option>, pub error: Option, pub metadata: HashMap, } /// Step status for detailed progress tracking #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum StepStatus { Pending, Running, Completed, Failed, Skipped, } /// Transaction priority levels #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)] pub enum TransactionPriority { Low = 1, Normal = 2, High = 3, Critical = 4, } /// Parameters for creating a transaction with steps #[derive(Debug, Clone)] pub struct TransactionCreationParams { pub transaction_type: TransactionType, pub user_id: u32, pub session_id: String, pub title: String, pub description: String, pub steps: Vec, pub priority: TransactionPriority, } /// Transaction result information #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TransactionResult { pub success: bool, pub message: String, pub details: Option, pub rollback_required: bool, pub steps_completed: usize, pub steps_total: usize, pub warnings: Vec, } /// Transaction object representing a single operation #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Transaction { pub id: String, pub transaction_type: TransactionType, pub user_id: u32, pub session_id: String, pub title: String, pub description: String, pub state: TransactionState, pub created_at: DateTime, pub started_at: Option>, pub completed_at: Option>, pub progress: f64, // 0.0 to 1.0 pub result: Option, pub metadata: HashMap, pub upgrade_data: Option, pub steps: Vec, pub dependencies: Vec, pub priority: TransactionPriority, pub estimated_duration: Option, // in seconds pub actual_duration: Option, // in seconds } impl Transaction { /// Create a new transaction pub fn new( id: String, transaction_type: TransactionType, user_id: u32, session_id: String, title: String, description: String, created_at: DateTime, ) -> Self { Self { id, transaction_type, user_id, session_id, title, description, state: TransactionState::Initialized, created_at, started_at: None, completed_at: None, progress: 0.0, result: None, metadata: HashMap::new(), upgrade_data: None, steps: Vec::new(), dependencies: Vec::new(), priority: TransactionPriority::Normal, estimated_duration: None, actual_duration: None, } } /// Update transaction state pub fn update_state(&mut self, new_state: TransactionState) { self.state = new_state; match new_state { TransactionState::Running => { self.started_at = Some(Utc::now()); } TransactionState::Completed | TransactionState::Failed | TransactionState::RolledBack => { self.completed_at = Some(Utc::now()); } _ => {} } } /// Update progress pub fn update_progress(&mut self, progress: f64) { self.progress = progress.clamp(0.0, 1.0); } /// Set transaction result pub fn set_result(&mut self, result: TransactionResult) { self.result = Some(result); } /// Add metadata pub fn add_metadata(&mut self, key: String, value: String) { self.metadata.insert(key, value); } /// Add a transaction step pub fn add_step(&mut self, step: TransactionStep) { self.steps.push(step); } /// Update step status pub fn update_step_status(&mut self, step_id: &str, status: StepStatus) -> AptOstreeResult<()> { if let Some(step) = self.steps.iter_mut().find(|s| s.id == step_id) { step.status = status.clone(); match status { StepStatus::Running => { step.started_at = Some(Utc::now()); } StepStatus::Completed | StepStatus::Failed => { step.completed_at = Some(Utc::now()); } _ => {} } Ok(()) } else { Err(AptOstreeError::System(format!("Step {} not found", step_id))) } } /// Get step by ID pub fn get_step(&self, step_id: &str) -> Option<&TransactionStep> { self.steps.iter().find(|s| s.id == step_id) } /// Calculate overall progress from steps pub fn calculate_progress_from_steps(&mut self) { if self.steps.is_empty() { return; } let completed_steps = self.steps.iter() .filter(|s| s.status == StepStatus::Completed) .count(); let total_steps = self.steps.len(); self.progress = completed_steps as f64 / total_steps as f64; } /// Set transaction priority pub fn set_priority(&mut self, priority: TransactionPriority) { self.priority = priority; } /// Add dependency pub fn add_dependency(&mut self, transaction_id: String) { if !self.dependencies.contains(&transaction_id) { self.dependencies.push(transaction_id); } } /// Check if dependencies are satisfied pub fn dependencies_satisfied(&self, completed_transactions: &[String]) -> bool { self.dependencies.iter().all(|dep| completed_transactions.contains(dep)) } /// Check if transaction is active pub fn is_active(&self) -> bool { matches!( self.state, TransactionState::Preparing | TransactionState::Ready | TransactionState::Running | TransactionState::Paused ) } /// Check if transaction can be cancelled pub fn can_cancel(&self) -> bool { matches!( self.state, TransactionState::Preparing | TransactionState::Ready | TransactionState::Running | TransactionState::Paused ) } /// Check if transaction can be rolled back pub fn can_rollback(&self) -> bool { matches!( self.state, TransactionState::Completed | TransactionState::Failed ) } } /// Transaction manager for handling all transactions #[allow(dead_code, clippy::new_without_default)] pub struct TransactionManager { transactions: Arc>>, } impl TransactionManager { /// Create a new transaction manager pub fn new() -> Self { Self { transactions: Arc::new(RwLock::new(HashMap::new())), } } } impl Default for TransactionManager { fn default() -> Self { Self::new() } } impl TransactionManager { /// Create a new transaction pub async fn create_transaction( &self, transaction_type: TransactionType, user_id: u32, session_id: String, title: String, description: String, ) -> AptOstreeResult { let transaction_id = Uuid::new_v4().to_string(); let transaction = Transaction::new( transaction_id.clone(), transaction_type, user_id, session_id, title, description, Utc::now(), ); // Store transaction self.transactions .write() .await .insert(transaction_id.clone(), transaction); Ok(transaction_id) } /// Get a transaction by ID pub async fn get_transaction(&self, transaction_id: &str) -> AptOstreeResult { let transactions = self.transactions.read().await; transactions .get(transaction_id) .cloned() .ok_or_else(|| AptOstreeError::System(format!("Transaction {} not found", transaction_id))) } /// Update a transaction pub async fn update_transaction(&self, transaction: &Transaction) -> AptOstreeResult<()> { let mut transactions = self.transactions.write().await; transactions.insert(transaction.id.clone(), transaction.clone()); Ok(()) } /// List all transactions pub async fn list_transactions(&self) -> AptOstreeResult> { let transactions = self.transactions.read().await; Ok(transactions.values().cloned().collect()) } /// List active transactions pub async fn list_active_transactions(&self) -> AptOstreeResult> { let transactions = self.transactions.read().await; Ok(transactions .values() .filter(|t| t.is_active()) .cloned() .collect()) } /// Cancel a transaction pub async fn cancel_transaction(&self, transaction_id: &str) -> AptOstreeResult<()> { let mut transaction = self.get_transaction(transaction_id).await?; if !transaction.can_cancel() { return Err(AptOstreeError::System(format!( "Transaction {} cannot be cancelled in state {:?}", transaction_id, transaction.state ))); } transaction.update_state(TransactionState::Cancelled); self.update_transaction(&transaction).await?; Ok(()) } /// Rollback a transaction pub async fn rollback_transaction(&self, transaction_id: &str) -> AptOstreeResult<()> { let mut transaction = self.get_transaction(transaction_id).await?; if !transaction.can_rollback() { return Err(AptOstreeError::System(format!( "Transaction {} cannot be rolled back in state {:?}", transaction_id, transaction.state ))); } transaction.update_state(TransactionState::RollingBack); self.update_transaction(&transaction).await?; // TODO: Implement actual rollback logic based on transaction type transaction.update_state(TransactionState::RolledBack); self.update_transaction(&transaction).await?; Ok(()) } /// Create an upgrade transaction pub async fn create_upgrade_transaction( &self, user_id: u32, session_id: String, upgrade_data: UpgradeTransaction, ) -> AptOstreeResult { let transaction_id = Uuid::new_v4().to_string(); let mut transaction = Transaction::new( transaction_id.clone(), TransactionType::Upgrade, user_id, session_id, "System Upgrade".to_string(), "Upgrading system packages and OSTree tree".to_string(), Utc::now(), ); transaction.upgrade_data = Some(upgrade_data); // Store transaction self.transactions .write() .await .insert(transaction_id.clone(), transaction); Ok(transaction_id) } /// Execute an upgrade transaction pub async fn execute_upgrade_transaction(&self, transaction_id: &str) -> AptOstreeResult<()> { let mut transaction = self.get_transaction(transaction_id).await?; if transaction.transaction_type != TransactionType::Upgrade { return Err(AptOstreeError::System( "Transaction is not an upgrade transaction".to_string() )); } // Extract upgrade data before borrowing let _packages_to_install = if let Some(ref upgrade_data) = transaction.upgrade_data { upgrade_data.packages_to_install.clone() } else { return Err(AptOstreeError::System( "Upgrade transaction data not found".to_string() )); }; let _packages_to_remove = if let Some(ref upgrade_data) = transaction.upgrade_data { upgrade_data.packages_to_remove.len() } else { 0 }; // Create upgrade steps if they don't exist if transaction.steps.is_empty() { let upgrade_steps = vec![ TransactionStep { id: "validate".to_string(), name: "Validate System".to_string(), description: "Check OSTree system status and APT availability".to_string(), status: StepStatus::Pending, progress: 0.0, started_at: None, completed_at: None, error: None, metadata: HashMap::new(), }, TransactionStep { id: "update_cache".to_string(), name: "Update APT Cache".to_string(), description: "Refresh package lists and metadata".to_string(), status: StepStatus::Pending, progress: 0.0, started_at: None, completed_at: None, error: None, metadata: HashMap::new(), }, TransactionStep { id: "check_updates".to_string(), name: "Check for Updates".to_string(), description: "Identify available system and package updates".to_string(), status: StepStatus::Pending, progress: 0.0, started_at: None, completed_at: None, error: None, metadata: HashMap::new(), }, TransactionStep { id: "download_packages".to_string(), name: "Download Packages".to_string(), description: "Download required package updates".to_string(), status: StepStatus::Pending, progress: 0.0, started_at: None, completed_at: None, error: None, metadata: HashMap::new(), }, TransactionStep { id: "apply_updates".to_string(), name: "Apply Updates".to_string(), description: "Apply package updates and system changes".to_string(), status: StepStatus::Pending, progress: 0.0, started_at: None, completed_at: None, error: None, metadata: HashMap::new(), }, TransactionStep { id: "finalize".to_string(), name: "Finalize Upgrade".to_string(), description: "Complete upgrade and prepare for reboot if needed".to_string(), status: StepStatus::Pending, progress: 0.0, started_at: None, completed_at: None, error: None, metadata: HashMap::new(), }, ]; for step in upgrade_steps { transaction.add_step(step); } } // Update transaction state transaction.update_state(TransactionState::Preparing); transaction.update_progress(0.1); self.update_transaction(&transaction).await?; // Execute upgrade steps let step_executor = |step: &TransactionStep| -> AptOstreeResult { match step.id.as_str() { "validate" => { // TODO: Implement real validation logic Ok(true) } "update_cache" => { // TODO: Implement real APT cache update Ok(true) } "check_updates" => { // TODO: Implement real update checking Ok(true) } "download_packages" => { // TODO: Implement real package downloading Ok(true) } "apply_updates" => { // TODO: Implement real update application Ok(true) } "finalize" => { // TODO: Implement real upgrade finalization Ok(true) } _ => Ok(true), } }; // Execute the transaction with steps let result = self.execute_transaction_with_steps(transaction_id, step_executor).await?; // Update the transaction with the result let mut transaction = self.get_transaction(transaction_id).await?; transaction.result = Some(result); self.update_transaction(&transaction).await?; Ok(()) } /// Clean up completed transactions pub async fn cleanup_completed_transactions(&self, max_age_hours: u64) -> AptOstreeResult { let mut transactions = self.transactions.write().await; let cutoff_time = Utc::now() - chrono::Duration::hours(max_age_hours as i64); let completed_ids: Vec = transactions .iter() .filter(|(_, t)| { matches!( t.state, TransactionState::Completed | TransactionState::Failed | TransactionState::Cancelled | TransactionState::RolledBack ) && t.completed_at.is_some_and(|time| time < cutoff_time) }) .map(|(id, _)| id.clone()) .collect(); let count = completed_ids.len(); for id in completed_ids { transactions.remove(&id); } Ok(count) } /// Create a transaction with steps #[allow(clippy::too_many_arguments)] pub async fn create_transaction_with_steps( &self, params: TransactionCreationParams, ) -> AptOstreeResult { let transaction_id = Uuid::new_v4().to_string(); let mut transaction = Transaction::new( transaction_id.clone(), params.transaction_type, params.user_id, params.session_id, params.title, params.description, Utc::now(), ); transaction.set_priority(params.priority); for step in params.steps { transaction.add_step(step); } // Store transaction self.transactions .write() .await .insert(transaction_id.clone(), transaction); Ok(transaction_id) } /// Execute a transaction with step-by-step progress pub async fn execute_transaction_with_steps( &self, transaction_id: &str, step_executor: impl Fn(&TransactionStep) -> AptOstreeResult + Send + Sync, ) -> AptOstreeResult { let mut transaction = self.get_transaction(transaction_id).await?; if !transaction.is_active() { return Err(AptOstreeError::System( "Transaction is not in an active state".to_string() )); } transaction.update_state(TransactionState::Running); self.update_transaction(&transaction).await?; let start_time = std::time::Instant::now(); let mut step_results = Vec::new(); // Execute each step and collect results for step in &transaction.steps { if step.status == StepStatus::Pending { let mut step_result = step.clone(); step_result.status = StepStatus::Running; step_result.started_at = Some(Utc::now()); // Execute the step let step_success = match step_executor(step) { Ok(success) => success, Err(e) => { step_result.status = StepStatus::Failed; step_result.error = Some(e.to_string()); false } }; if step_success { step_result.status = StepStatus::Completed; step_result.completed_at = Some(Utc::now()); } else { step_result.status = StepStatus::Failed; if step_result.error.is_none() { step_result.error = Some("Step execution failed".to_string()); } } // Check if any step failed if step_result.status == StepStatus::Failed { let error_message = step_result.error.clone(); let step_name = step_result.name.clone(); step_results.push(step_result); transaction.update_state(TransactionState::Failed); transaction.actual_duration = Some(start_time.elapsed().as_secs()); // Update steps in transaction for (i, result) in step_results.iter().enumerate() { if i < transaction.steps.len() { transaction.steps[i] = result.clone(); } } self.update_transaction(&transaction).await?; return Ok(TransactionResult { success: false, message: format!("Step '{}' failed", step_name), details: error_message, rollback_required: true, steps_completed: step_results.iter().filter(|s| s.status == StepStatus::Completed).count(), steps_total: transaction.steps.len(), warnings: Vec::new(), }); } } else { step_results.push(step.clone()); } } // Update transaction with step results transaction.steps = step_results; transaction.calculate_progress_from_steps(); // All steps completed successfully transaction.update_state(TransactionState::Completed); transaction.actual_duration = Some(start_time.elapsed().as_secs()); let result = TransactionResult { success: true, message: "Transaction completed successfully".to_string(), details: None, rollback_required: false, steps_completed: transaction.steps.len(), steps_total: transaction.steps.len(), warnings: Vec::new(), }; transaction.result = Some(result.clone()); self.update_transaction(&transaction).await?; Ok(result) } /// Get transactions by priority pub async fn get_transactions_by_priority(&self, priority: TransactionPriority) -> AptOstreeResult> { let transactions = self.transactions.read().await; Ok(transactions .values() .filter(|t| t.priority == priority) .cloned() .collect()) } /// Get transactions with dependencies pub async fn get_transactions_with_dependencies(&self) -> AptOstreeResult> { let transactions = self.transactions.read().await; Ok(transactions .values() .filter(|t| !t.dependencies.is_empty()) .cloned() .collect()) } /// Validate transaction dependencies pub async fn validate_transaction_dependencies(&self, transaction_id: &str) -> AptOstreeResult { let transaction = self.get_transaction(transaction_id).await?; let completed_transactions = self.list_transactions().await? .into_iter() .filter(|t| t.state == TransactionState::Completed) .map(|t| t.id) .collect::>(); Ok(transaction.dependencies_satisfied(&completed_transactions)) } }