# frozen_string_literal: true # ========================================================== # adn/tools/bkps/lib/proc_xen.rb — Procesador XEN (.xva) # Migrado desde dtic-BKPs v6.0 → ADN::BKPs # # Fase 2: Soporta orquestación 1-dron-por-archivo # listar(tarea) → lista archivos pendientes # ejecutar_uno(tarea, arch) → comprime UN solo archivo # ejecutar(tarea) → comprime TODOS (legacy) # ========================================================== require 'shellwords' require 'open3' require 'fileutils' module ADN module BKPs module Procesador class Xen def initialize(log) @log = log @pv = system('which pv > /dev/null 2>&1') end # Scanner: devuelve lista de archivos .xva pendientes de procesar def listar(tarea) archivos = Dir.glob(File.join(tarea[:origen], '*.xva')) archivos.map do |ruta_xva| nombre_xva = File.basename(ruta_xva) nombre_vm = nombre_xva.split('_').first dir_dest = File.join(tarea[:destino], nombre_vm) nombre_sin_ext = File.basename(nombre_xva, '.xva') ruta_tar = File.join(dir_dest, "#{nombre_sin_ext}.tar.gz") { archivo: nombre_xva, ruta: ruta_xva, vm: nombre_vm, destino: ruta_tar, tamaño: File.size(ruta_xva), pendiente: !File.exist?(ruta_tar) || tarea[:sobrescribir] } end end # Atómico: comprime UN solo archivo .xva def ejecutar_uno(tarea, nombre_archivo) ruta_xva = File.join(tarea[:origen], nombre_archivo) unless File.exist?(ruta_xva) @log.error("Archivo no encontrado: #{nombre_archivo}") return false end nombre_vm = nombre_archivo.split('_').first dir_dest = File.join(tarea[:destino], nombre_vm) FileUtils.mkdir_p(dir_dest) nombre_sin_ext = File.basename(nombre_archivo, '.xva') ruta_tar = File.join(dir_dest, "#{nombre_sin_ext}.tar.gz") if File.exist?(ruta_tar) && !tarea[:sobrescribir] @log.info("#{File.basename(ruta_tar)} existe. Saltando.") return true end comprimir(tarea, ruta_xva, nombre_archivo, ruta_tar) end # Legacy: procesa TODOS los archivos (compatibilidad con sistema previo) def ejecutar(tarea) @log.info("--- #{tarea[:texto]} ---") @log.info("#{tarea[:id]}: Origen: #{tarea[:origen]}") archivos = Dir.glob(File.join(tarea[:origen], '*.xva')) if archivos.empty? @log.info("#{tarea[:id]}: Sin archivos .xva en origen.") return true end @log.info("Encontrados #{archivos.length} archivo(s) .xva.") procesados = 0 archivos.each do |ruta_xva| nombre_xva = File.basename(ruta_xva) nombre_vm = nombre_xva.split('_').first dir_dest = File.join(tarea[:destino], nombre_vm) FileUtils.mkdir_p(dir_dest) nombre_sin_ext = File.basename(nombre_xva, '.xva') ruta_tar = File.join(dir_dest, "#{nombre_sin_ext}.tar.gz") if File.exist?(ruta_tar) if tarea[:sobrescribir] @log.info("#{File.basename(ruta_tar)} existe. Sobrescribiendo.") else @log.info("#{File.basename(ruta_tar)} existe. Saltando.") next end end procesados += 1 if comprimir(tarea, ruta_xva, nombre_xva, ruta_tar) end @log.info("✔ #{procesados} archivos .xva procesados.") true end private def comprimir(tarea, ruta_xva, nombre_xva, ruta_tar) @log.info("Comprimiendo #{nombre_xva}") if @pv cmd_tar = ['tar', '-czf', '-', '-C', tarea[:origen], nombre_xva] cmd_pv = ['pv', '-s', File.size(ruta_xva).to_s] File.open(ruta_tar, 'wb') do |f| Open3.pipeline(cmd_tar, cmd_pv, out: f).each_with_index do |s, i| raise "Falló #{%w[tar pv][i]} (#{s.exitstatus})" unless s.success? end end else cmd = "tar -czf #{Shellwords.escape(ruta_tar)} -C #{Shellwords.escape(tarea[:origen])} #{Shellwords.escape(nombre_xva)}" system(cmd) or raise "tar falló (#{$?.exitstatus})" end @log.info("✔ Compresión OK: #{File.basename(ruta_tar)}") if tarea[:eliminar_origen] FileUtils.rm_f(ruta_xva) @log.info("XVA original eliminado.") end true rescue => e @log.error("#{tarea[:id]}: Error comprimiendo #{nombre_xva}: #{e.message}") FileUtils.rm_f(ruta_tar) false end end end end end