use crate::models::image::{Estadisticas, InfoImagen};
use crate::models::partition::{EsquemaParticion, Particion, SistemaOperativo};
use crate::models::software::{Programa, VMInfo};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicU8, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct CancellationToken {
inner: Arc<AtomicBool>,
}
impl Default for CancellationToken {
fn default() -> Self {
Self::new()
}
}
impl CancellationToken {
pub fn new() -> Self {
Self {
inner: Arc::new(AtomicBool::new(false)),
}
}
pub fn from_arc(inner: Arc<AtomicBool>) -> Self {
Self { inner }
}
pub fn cancel(&self) {
self.inner.store(true, Ordering::Release);
}
pub fn cancelar(&self) {
self.cancel();
}
pub fn is_cancelled(&self) -> bool {
self.inner.load(Ordering::Acquire)
}
pub fn esta_cancelado(&self) -> bool {
self.is_cancelled()
}
pub fn as_arc(&self) -> &Arc<AtomicBool> {
&self.inner
}
pub fn clone_arc(&self) -> Arc<AtomicBool> {
self.inner.clone()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProgresoSnapshot {
pub porcentaje: u8,
pub etapa_id: u8,
pub tareas_completadas: usize,
pub total_tareas: usize,
pub bytes_procesados: u64,
pub bytes_totales: u64,
pub cancelado: bool,
}
#[derive(Debug)]
pub struct InspectionProgress {
pub total_tareas: AtomicUsize,
pub tareas_completadas: AtomicUsize,
pub bytes_procesados: AtomicU64,
pub bytes_totales: AtomicU64,
pub porcentaje: AtomicU8,
pub etapa_id: AtomicU8,
pub cancelado: AtomicBool,
}
impl Default for InspectionProgress {
fn default() -> Self {
Self::new()
}
}
impl InspectionProgress {
pub fn new() -> Self {
Self {
total_tareas: AtomicUsize::new(0),
tareas_completadas: AtomicUsize::new(0),
bytes_procesados: AtomicU64::new(0),
bytes_totales: AtomicU64::new(0),
porcentaje: AtomicU8::new(0),
etapa_id: AtomicU8::new(0),
cancelado: AtomicBool::new(false),
}
}
pub fn con_token_cancelacion(token: Option<&Arc<AtomicBool>>) -> Self {
let cancelado = if let Some(t) = token {
AtomicBool::new(t.load(Ordering::Acquire))
} else {
AtomicBool::new(false)
};
Self {
total_tareas: AtomicUsize::new(0),
tareas_completadas: AtomicUsize::new(0),
bytes_procesados: AtomicU64::new(0),
bytes_totales: AtomicU64::new(0),
porcentaje: AtomicU8::new(0),
etapa_id: AtomicU8::new(0),
cancelado,
}
}
#[inline]
pub fn completion_percentage(&self) -> f32 {
let pct = self.porcentaje.load(Ordering::Relaxed);
let total = self.total_tareas.load(Ordering::Relaxed);
if total > 0 {
let done = self.tareas_completadas.load(Ordering::Relaxed);
let calc = (done as f32 / total as f32) * 100.0;
calc.clamp(pct as f32, 100.0)
} else {
(pct.min(100)) as f32
}
}
#[inline]
pub fn porcentaje_completitud(&self) -> f32 {
self.completion_percentage()
}
#[inline]
pub fn is_cancelled(&self) -> bool {
self.cancelado.load(Ordering::Acquire)
}
#[inline]
pub fn esta_cancelado(&self) -> bool {
self.is_cancelled()
}
#[inline]
pub fn cancel(&self) {
self.cancelado.store(true, Ordering::Release);
}
#[inline]
pub fn cancelar(&self) {
self.cancel();
}
#[inline]
pub fn set_percentage(&self, pct: u8) {
self.porcentaje.store(pct.min(100), Ordering::Relaxed);
}
#[inline]
pub fn set_stage_id(&self, stage: u8) {
self.etapa_id.store(stage, Ordering::Relaxed);
}
#[inline]
pub fn stage_id(&self) -> u8 {
self.etapa_id.load(Ordering::Relaxed)
}
#[inline]
pub fn add_bytes_processed(&self, bytes: u64) {
self.bytes_procesados.fetch_add(bytes, Ordering::Relaxed);
}
#[inline]
pub fn bytes_processed(&self) -> u64 {
self.bytes_procesados.load(Ordering::Relaxed)
}
#[inline]
pub fn set_total_bytes(&self, total: u64) {
self.bytes_totales.store(total, Ordering::Relaxed);
}
#[inline]
pub fn total_bytes(&self) -> u64 {
self.bytes_totales.load(Ordering::Relaxed)
}
#[inline]
pub fn set_total_tasks(&self, total: usize) {
self.total_tareas.store(total, Ordering::Relaxed);
}
#[inline]
pub fn total_tasks(&self) -> usize {
self.total_tareas.load(Ordering::Relaxed)
}
#[inline]
pub fn increment_completed_tasks(&self) {
self.tareas_completadas.fetch_add(1, Ordering::Relaxed);
}
#[inline]
pub fn completed_tasks(&self) -> usize {
self.tareas_completadas.load(Ordering::Relaxed)
}
pub fn snapshot(&self) -> ProgresoSnapshot {
ProgresoSnapshot {
porcentaje: self.porcentaje.load(Ordering::Relaxed),
etapa_id: self.etapa_id.load(Ordering::Relaxed),
tareas_completadas: self.tareas_completadas.load(Ordering::Relaxed),
total_tareas: self.total_tareas.load(Ordering::Relaxed),
bytes_procesados: self.bytes_procesados.load(Ordering::Relaxed),
bytes_totales: self.bytes_totales.load(Ordering::Relaxed),
cancelado: self.cancelado.load(Ordering::Acquire),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Opciones {
pub noapps: bool,
pub nosystem: bool,
pub incluir_system: bool,
pub qemu_nbd: Option<PathBuf>,
pub tamano_chunk: Option<u64>,
pub forzar_nbd: bool,
pub socket_unix: Option<PathBuf>,
pub args_extra_nbd: Vec<String>,
pub timeout_conexion: Option<Duration>,
pub persistente_nbd: bool,
#[serde(skip)]
pub cancel_token: Option<Arc<AtomicBool>>,
}
pub type OpcionesInspeccion = Opciones;
impl Opciones {
#[inline]
pub fn debe_analizar_apps(&self) -> bool {
!self.noapps
}
#[inline]
pub fn should_analyze_apps(&self) -> bool {
self.debe_analizar_apps()
}
#[inline]
pub fn debe_analizar_sistema(&self) -> bool {
!self.nosystem
}
#[inline]
pub fn should_analyze_system(&self) -> bool {
self.debe_analizar_sistema()
}
pub fn with_qemu_nbd(mut self, path: PathBuf) -> Self {
self.qemu_nbd = Some(path);
self
}
pub fn with_forzar_nbd(mut self, forzar: bool) -> Self {
self.forzar_nbd = forzar;
self
}
pub fn with_socket_unix(mut self, ruta: impl Into<PathBuf>) -> Self {
self.socket_unix = Some(ruta.into());
self
}
pub fn with_extra_nbd_args(mut self, args: Vec<String>) -> Self {
self.args_extra_nbd = args;
self
}
pub fn with_connection_timeout(mut self, timeout: Duration) -> Self {
self.timeout_conexion = Some(timeout);
self
}
pub fn with_persistente_nbd(mut self, persistente: bool) -> Self {
self.persistente_nbd = persistente;
self
}
pub fn with_cancel_token(mut self, token: Arc<AtomicBool>) -> Self {
self.cancel_token = Some(token);
self
}
pub fn with_cancellation_token(mut self, token: &CancellationToken) -> Self {
self.cancel_token = Some(token.clone_arc());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgresoInspeccion {
pub porcentaje: u8,
pub etapa: String,
pub detalle: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InformeInspeccion {
pub imagen: InfoImagen,
pub esquema: EsquemaParticion,
pub particiones: Vec<Particion>,
pub sistema_operativo: SistemaOperativo,
pub vm_info: VMInfo,
pub programas: Vec<Programa>,
#[serde(default)]
pub advertencias: Vec<String>,
pub estadisticas: Estadisticas,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_opciones_defecto() {
let opc = Opciones::default();
assert!(!opc.noapps);
assert!(!opc.nosystem);
assert!(opc.debe_analizar_apps());
assert!(opc.debe_analizar_sistema());
}
#[test]
fn test_opciones_alias_ingles_should_analyze() {
let opc = Opciones {
noapps: true,
..Opciones::default()
};
assert_eq!(opc.should_analyze_apps(), opc.debe_analizar_apps());
assert_eq!(opc.should_analyze_system(), opc.debe_analizar_sistema());
assert!(!opc.should_analyze_apps());
assert!(opc.should_analyze_system());
}
#[test]
fn test_opciones_noapps() {
let opc = Opciones {
noapps: true,
..Opciones::default()
};
assert!(!opc.debe_analizar_apps());
assert!(opc.debe_analizar_sistema());
}
#[test]
fn test_opciones_nosystem() {
let opc = Opciones {
nosystem: true,
..Opciones::default()
};
assert!(opc.debe_analizar_apps());
assert!(!opc.debe_analizar_sistema());
}
#[test]
fn test_opciones_nbd_avanzadas() {
let opc = Opciones::default()
.with_socket_unix("/tmp/qemu-test.sock")
.with_extra_nbd_args(vec!["--cache=none".into(), "--detect-zeroes=on".into()])
.with_connection_timeout(Duration::from_secs(10))
.with_persistente_nbd(true);
assert_eq!(opc.socket_unix, Some(PathBuf::from("/tmp/qemu-test.sock")));
assert_eq!(
opc.args_extra_nbd,
vec!["--cache=none".to_string(), "--detect-zeroes=on".to_string()]
);
assert_eq!(opc.timeout_conexion, Some(Duration::from_secs(10)));
assert!(opc.persistente_nbd);
}
}