🐝 F2.T1c: Atomización rclone — 1 dron por directorio de nodo
- sync_rclone.rb: listar() + ejecutar_uno() implementados - Soporta origen local (Dir.children) y remoto (rclone lsf) - Orquestador: nota dinámica (Sync vs Comprimir según tipo) - Validado dry-run: T4 (2 dirs), T5 (17 dirs), T6 (2 dirs) - Legacy ejecutar() preservado para compatibilidad
This commit is contained in:
@@ -3,6 +3,11 @@
|
||||
# ==========================================================
|
||||
# adn/tools/bkps/lib/sync_rclone.rb — Sincronización rclone
|
||||
# Migrado desde dtic-BKPs v6.0 → ADN::BKPs
|
||||
#
|
||||
# Fase 2: Soporta orquestación 1-dron-por-directorio
|
||||
# listar(tarea) → lista subdirectorios (nodos)
|
||||
# ejecutar_uno(tarea, nodo) → sincroniza UN solo directorio
|
||||
# ejecutar(tarea) → sincroniza TODO el origen (legacy)
|
||||
# ==========================================================
|
||||
|
||||
require 'shellwords'
|
||||
@@ -26,6 +31,32 @@ module ADN
|
||||
@log = log
|
||||
end
|
||||
|
||||
# Scanner: devuelve lista de subdirectorios (nodos) en el origen
|
||||
# Compatible con el orquestador que espera {archivo:, vm:, tamaño:, pendiente:}
|
||||
def listar(tarea)
|
||||
origen = tarea[:origen]
|
||||
|
||||
# Determinar si el origen es local o remoto (rclone remote)
|
||||
if origen.include?(':')
|
||||
listar_remoto(tarea)
|
||||
else
|
||||
listar_local(tarea)
|
||||
end
|
||||
end
|
||||
|
||||
# Atómico: sincroniza UN solo directorio de nodo
|
||||
def ejecutar_uno(tarea, nombre_directorio)
|
||||
origen = File.join(tarea[:origen], nombre_directorio, '')
|
||||
destino = File.join(tarea[:destino], nombre_directorio, '')
|
||||
|
||||
@log.info("☁️ Sync atómico: #{nombre_directorio}")
|
||||
@log.info(" Origen: #{origen}")
|
||||
@log.info(" Destino: #{destino}")
|
||||
|
||||
sincronizar(tarea, origen, destino)
|
||||
end
|
||||
|
||||
# Legacy: sincroniza TODO el origen (compatibilidad con sistema previo)
|
||||
def ejecutar(tarea)
|
||||
@log.info("--- #{tarea[:texto]} ---")
|
||||
origen = tarea[:origen]
|
||||
@@ -34,8 +65,72 @@ module ADN
|
||||
@log.info("Origen: #{origen}")
|
||||
@log.info("Destino: #{destino}")
|
||||
|
||||
sincronizar(tarea, origen, destino)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# ─── Listado local ──────────────────────────────────────────
|
||||
def listar_local(tarea)
|
||||
origen = tarea[:origen]
|
||||
destino = tarea[:destino]
|
||||
|
||||
unless Dir.exist?(origen)
|
||||
@log.error("Directorio origen no existe: #{origen}")
|
||||
return []
|
||||
end
|
||||
|
||||
Dir.children(origen)
|
||||
.select { |d| File.directory?(File.join(origen, d)) }
|
||||
.sort
|
||||
.map do |nombre|
|
||||
ruta_local = File.join(origen, nombre)
|
||||
tamaño = calcular_tamaño_dir(ruta_local)
|
||||
|
||||
{
|
||||
archivo: nombre, # Identificador para ejecutar_uno
|
||||
vm: nombre, # Nombre visible (nodo)
|
||||
ruta: ruta_local,
|
||||
destino: File.join(destino, nombre, ''),
|
||||
tamaño: tamaño,
|
||||
pendiente: true # Siempre sincronizar (rclone decide deltas)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Listado remoto (rclone lsf) ────────────────────────────
|
||||
def listar_remoto(tarea)
|
||||
origen = tarea[:origen]
|
||||
destino = tarea[:destino]
|
||||
|
||||
cmd = "rclone lsf #{Shellwords.escape(origen)} --dirs-only 2>/dev/null"
|
||||
output = `#{cmd}`
|
||||
|
||||
unless $?.success?
|
||||
@log.error("No se pudo listar remoto: #{origen}")
|
||||
return []
|
||||
end
|
||||
|
||||
output.split("\n")
|
||||
.map { |d| d.chomp('/').strip }
|
||||
.reject(&:empty?)
|
||||
.sort
|
||||
.map do |nombre|
|
||||
{
|
||||
archivo: nombre,
|
||||
vm: nombre,
|
||||
ruta: File.join(origen, nombre, ''),
|
||||
destino: File.join(destino, nombre, ''),
|
||||
tamaño: 0, # No calculamos tamaño remoto (costoso)
|
||||
pendiente: true
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# ─── Sincronización ─────────────────────────────────────────
|
||||
def sincronizar(_tarea, origen, destino)
|
||||
cmd = "rclone sync #{Shellwords.escape(origen)} #{Shellwords.escape(destino)} #{RCLONE_OPTS}"
|
||||
@log.info("#{tarea[:id]}: Ejecutando → #{cmd}")
|
||||
@log.info("Ejecutando → #{cmd}")
|
||||
|
||||
exito = system(cmd)
|
||||
codigo = $?.exitstatus
|
||||
@@ -43,11 +138,19 @@ module ADN
|
||||
if exito
|
||||
@log.info("✔ Sincronización rclone completada.")
|
||||
else
|
||||
@log.error("#{tarea[:id]}: Fallo en sincronización (código: #{codigo}).")
|
||||
@log.error("Fallo en sincronización (código: #{codigo}).")
|
||||
end
|
||||
|
||||
exito
|
||||
end
|
||||
|
||||
# ─── Helpers ────────────────────────────────────────────────
|
||||
def calcular_tamaño_dir(ruta)
|
||||
output = `du -sb #{Shellwords.escape(ruta)} 2>/dev/null`.strip
|
||||
output.split("\t").first.to_i
|
||||
rescue
|
||||
0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user