Skip to content

Commit

Permalink
feat: add transfer command and implementation for database dump transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
Kremilly committed Dec 6, 2024
1 parent d4f8811 commit 9195870
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/args_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -62,3 +65,14 @@ pub struct ImportOptions {
/// Dump file path
pub file: Option<String>,
}

#[derive(Parser)]
pub struct TransferOptions {
#[arg(short, long)]
/// Database name
pub database: Option<String>,

#[arg(short, long)]
/// Dump file path
pub file: Option<String>,
}
25 changes: 25 additions & 0 deletions src/dump_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<u64>()
.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<dyn Error>> {
let cli = Cli::parse();

Expand All @@ -101,6 +122,10 @@ impl DumpSync {
Commands::Init => {
self.initialize().await?;
},

Commands::Transfer(options) => {
self.transfer(options);
},
}

Ok(())
Expand Down
13 changes: 13 additions & 0 deletions src/engine/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use crate::{
engine::{
export::Export,
import::Import,
transfer::Transfer,
},
};

Expand Down Expand Up @@ -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");
}

}
1 change: 1 addition & 0 deletions src/engine/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod dump;
pub mod export;
pub mod import;
pub mod transfer;
pub mod connection;
129 changes: 129 additions & 0 deletions src/engine/transfer.rs
Original file line number Diff line number Diff line change
@@ -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<PathBuf, Box<dyn std::error::Error>> {
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<dyn Error>> {
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(())
}

}

0 comments on commit 9195870

Please sign in to comment.