From 919587094ca89eb3f4d4153bb27a787417a3d6c0 Mon Sep 17 00:00:00 2001 From: Kremilly Date: Fri, 6 Dec 2024 15:56:55 -0300 Subject: [PATCH] feat: add transfer command and implementation for database dump transfer --- src/args_cli.rs | 14 +++++ src/dump_sync.rs | 25 ++++++++ src/engine/dump.rs | 13 +++++ src/engine/mod.rs | 1 + src/engine/transfer.rs | 129 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+) create mode 100644 src/engine/transfer.rs diff --git a/src/args_cli.rs b/src/args_cli.rs index db10176..533ffca 100644 --- a/src/args_cli.rs +++ b/src/args_cli.rs @@ -32,6 +32,9 @@ pub enum Commands { /// Import the database dump Import(ImportOptions), + + /// Transfer the dump to other server + Transfer(TransferOptions), /// Initialize the new dump sync project Init, @@ -62,3 +65,14 @@ pub struct ImportOptions { /// Dump file path pub file: Option, } + +#[derive(Parser)] +pub struct TransferOptions { + #[arg(short, long)] + /// Database name + pub database: Option, + + #[arg(short, long)] + /// Dump file path + pub file: Option, +} diff --git a/src/dump_sync.rs b/src/dump_sync.rs index 734cb2a..a269bf2 100644 --- a/src/dump_sync.rs +++ b/src/dump_sync.rs @@ -86,6 +86,27 @@ impl DumpSync { Dump::new(&host, port, &user, &password, &dbname, &backup_path, Some(interval), &backup_path).export(); } + fn transfer(&self, options: TransferOptions) { + Env::new(); + UI::header(); + + let backup_path = options.file.unwrap(); + let dbname = std::env::var("DS_TRANSFER_DB_NAME").or_else(|_| std::env::var("DS_TRANSFER_DB_NAME")).unwrap_or_default(); + + let host = std::env::var("DS_TRANSFER_HOST").or_else(|_| std::env::var("DS_TRANSFER_HOST")).unwrap_or_default(); + let user = std::env::var("DS_TRANSFER_USER").or_else(|_| std::env::var("DS_TRANSFER_USER")).unwrap_or_default(); + let password = std::env::var("DS_TRANSFER_PASSWORD").or_else(|_| std::env::var("DS_TRANSFER_PASSWORD")).unwrap_or_default(); + + let port = std::env::var("DS_TRANSFER_PORT") + .or_else(|_| std::env::var("DS_TRANSFER_DB_PORT")) + .unwrap_or_default() + .parse::() + .expect("Invalid port"); + + UI::section_header("Importing dump to server", "info"); + Dump::new(&host, port, &user, &password, &dbname, &backup_path, None, &backup_path).transfer(); + } + pub async fn init(&self) -> Result<(), Box> { let cli = Cli::parse(); @@ -101,6 +122,10 @@ impl DumpSync { Commands::Init => { self.initialize().await?; }, + + Commands::Transfer(options) => { + self.transfer(options); + }, } Ok(()) diff --git a/src/engine/dump.rs b/src/engine/dump.rs index 5938cec..3a207ad 100644 --- a/src/engine/dump.rs +++ b/src/engine/dump.rs @@ -25,6 +25,7 @@ use crate::{ engine::{ export::Export, import::Import, + transfer::Transfer, }, }; @@ -145,4 +146,16 @@ impl Dump { ).dump().expect("Failed to import dump"); } + pub fn transfer(&self) { + Transfer::new( + &self.host, + self.port as u16, + &self.user, + &self.password, + &self.dbname, + &self.dump_file_path, + &self.path, + ).dump().expect("Failed to transfer dump"); + } + } diff --git a/src/engine/mod.rs b/src/engine/mod.rs index 74482ad..0dfcbc8 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -1,4 +1,5 @@ pub mod dump; pub mod export; pub mod import; +pub mod transfer; pub mod connection; \ No newline at end of file diff --git a/src/engine/transfer.rs b/src/engine/transfer.rs new file mode 100644 index 0000000..3c1c655 --- /dev/null +++ b/src/engine/transfer.rs @@ -0,0 +1,129 @@ +use regex::Regex; +use flate2::read::GzDecoder; + +use mysql::{ + *, + prelude::* +}; + +use std::{ + fs::File, + error::Error, + + path::{ + Path, + PathBuf + }, + + io::{ + Read, + BufReader, + }, +}; + +use crate::{ + engine::connection::Connection, + + ui::{ + errors_alerts::ErrorsAlerts, + success_alerts::SuccessAlerts, + }, +}; + +pub struct Transfer { + host: String, + port: u16, + user: String, + password: String, + dbname: String, + path: String, + dump_file_path: String, +} + +impl Transfer { + + pub fn new( + host: &str, + port: u16, + user: &str, + password: &str, + dbname: &str, + dump_file_path: &str, + path: &str, + ) -> Self { + Self { + host: host.to_string(), + port, + user: user.to_string(), + password: password.to_string(), + dbname: dbname.to_string(), + path: path.to_string(), + dump_file_path: dump_file_path.to_string(), + } + } + + fn complete_path(&self) -> Result> { + let path = Path::new(&self.dump_file_path); + + if path.is_absolute() { + Ok(path.to_path_buf()) + } else { + let dump_file_path = Path::new(&self.dump_file_path); + Ok(dump_file_path.join(&self.path)) + } + } + + pub fn dump(&self) -> Result<(), Box> { + let pool = Connection { + host: self.host.clone(), + port: self.port, + user: self.user.clone(), + password: self.password.clone(), + dbname: Some(self.dbname.clone()), + }.create_pool()?; + + let mut conn = pool.get_conn()?; + let is_compressed = self.dump_file_path.ends_with(".sql.gz"); + + let file = self.complete_path()?; + + let dump_content = if is_compressed { + let file = File::open(file)?; + + let mut decoder = GzDecoder::new(BufReader::new(file)); + let mut content = String::new(); + + decoder.read_to_string(&mut content)?; + content + } else { + let mut file = File::open(&self.dump_file_path)?; + let mut content = String::new(); + + file.read_to_string(&mut content)?; + content + }; + + let create_table_regex = Regex::new(r"(?i)CREATE TABLE\s+`?(\w+)`?").unwrap(); + + for statement in dump_content.split(';') { + let trimmed = statement.trim(); + + if !trimmed.is_empty() { + match conn.query_drop(trimmed) { + Ok(_) => { + if let Some(captures) = create_table_regex.captures(trimmed) { + if let Some(table_name) = captures.get(1) { + SuccessAlerts::table(table_name.as_str()); + } + } + } + Err(e) => ErrorsAlerts::import(&self.dbname, trimmed, &e.to_string()), + } + } + } + + SuccessAlerts::import(&self.dbname); + Ok(()) + } + +} \ No newline at end of file