diff --git a/Cargo.lock b/Cargo.lock index b59d9c7..2b36405 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,26 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "encoding_rs" version = "0.8.30" @@ -176,6 +196,7 @@ dependencies = [ name = "godaddy_ddns" version = "0.1.0" dependencies = [ + "dirs", "log", "reqwest", "serde", @@ -616,6 +637,16 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +dependencies = [ + "getrandom", + "redox_syscall", +] + [[package]] name = "remove_dir_all" version = "0.5.3" diff --git a/Cargo.toml b/Cargo.toml index 98b18ce..a6c7e0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,12 @@ serde = { version = "1.0.132", features = ["derive"]} serde_json = "1.0" log = { version = "0.4", features = ["std", "serde"] } simple_logger = "1.16.0" -strfmt = "0.1.6" \ No newline at end of file +strfmt = "0.1.6" +dirs = "4.0.0" + +[profile.release] +opt-level = 3 +lto = true +debug = false +codegen-units = 1 +panic = "abort" \ No newline at end of file diff --git a/src/file_handler/mod.rs b/src/file_handler/mod.rs new file mode 100644 index 0000000..be16774 --- /dev/null +++ b/src/file_handler/mod.rs @@ -0,0 +1,80 @@ +use std::fs::{create_dir, read_to_string, File}; +use std::io::{Error, ErrorKind, Write}; +use std::path::Path; + +use crate::file_handler::path_handler::{ + get_application_folder_path, get_ip_file_path, get_records_file_path, +}; + +pub mod path_handler; + +/// Sets up the application folder. +pub fn application_folder_setup() -> std::io::Result<()> { + let app_folder_path = get_application_folder_path(); + + if app_folder_path.is_none() { + return Err(Error::from(ErrorKind::NotFound)); + } + + let path = app_folder_path.unwrap(); + + if path.exists() { + return Ok(()); + } + + create_dir(&path) +} + +/// Gets the contents of the RECORDS_FILE. +pub fn get_records_file() -> String { + let path = get_records_file_path().expect("Couldn't get RECORDS_FILE path."); + + read_file(&path).expect(&format!( + "{} does not exist. Please create it.", + path.display() + )) +} + +/// Gets the contents of the IP_FILE. +pub fn get_ip_file() -> Option { + let path = get_ip_file_path().expect("Couldn't get IP_FILE path."); + + read_file(&path).ok() +} + +/// Stores the current IP value in the FILE_NAME. +/// +/// # Arguments +/// +/// * `content` - A &[u8] holding the current IP value. +pub fn set_ip_file(content: &[u8]) -> () { + let path = get_ip_file_path().expect("Couldn't get IP_FILE path."); + + write_file(&path, content) +} + +/// Reads a file and returns its contents as String if Ok(). +/// +/// # Arguments +/// +/// * `path` - A &Path holding the path of the file to be read. +fn read_file(path: &Path) -> std::io::Result { + read_to_string(path) +} + +/// Writes a file. +/// +/// # Arguments +/// +/// * `path` - A &Path holding the path of the file to be written. +/// +/// * `content` - A &[u8] holding the info that will be written to the file. +fn write_file(path: &Path, content: &[u8]) -> () { + let display = &path.display(); + + let mut file = + File::create(path).expect(&format!("Error opening or creating file: {}", display)); + + file.write_all(content) + .expect(&format!("Error writing file: {}", display)) +} diff --git a/src/file_handler/path_handler/mod.rs b/src/file_handler/path_handler/mod.rs new file mode 100644 index 0000000..087fa8d --- /dev/null +++ b/src/file_handler/path_handler/mod.rs @@ -0,0 +1,50 @@ +use std::path::PathBuf; + +const APP_DIR: &'static str = ".godaddy-ddns"; +const IP_FILE_NAME: &'static str = "ddns_ip"; +const RECORDS_FILE_NAME: &'static str = "records.json"; + +/// Gets the application folder path and returns it if found. +pub fn get_application_folder_path() -> Option { + let home = dirs::home_dir(); + + if home.is_none() { + return None; + } + + Some(PathBuf::from(format!( + "{}/{}", + home.unwrap().display(), + APP_DIR + ))) +} + +/// Gets the IP_FILE path and returns it if found. +pub fn get_ip_file_path() -> Option { + let app_folder = get_application_folder_path(); + + if app_folder.is_none() { + return None; + } + + Some(PathBuf::from(format!( + "{}/{}", + app_folder.unwrap().display(), + IP_FILE_NAME + ))) +} + +/// Gets the RECORDS_FILE path and returns it if found. +pub fn get_records_file_path() -> Option { + let app_folder = get_application_folder_path(); + + if app_folder.is_none() { + return None; + } + + Some(PathBuf::from(format!( + "{}/{}", + app_folder.unwrap().display(), + RECORDS_FILE_NAME + ))) +} diff --git a/src/ip_handler/mod.rs b/src/ip_handler/mod.rs index bc4c5f9..927a0d8 100644 --- a/src/ip_handler/mod.rs +++ b/src/ip_handler/mod.rs @@ -1,14 +1,10 @@ -use std::fs::{File, read_to_string}; -use std::io::Write; -use std::path::Path; - use log::debug; +use crate::file_handler::{get_ip_file, set_ip_file}; use crate::ip_handler::ip::IP; mod ip; -const IP_FILE_NAME: &'static str = "ddns_ip"; const WEBSITE_URL: &'static str = "https://httpbin.org/ip"; /// Returns an Option holding the current IP address if the value has changed, otherwise returns None. @@ -37,31 +33,12 @@ pub async fn get_ip_to_publish() -> Option { async fn check_current_ip() -> Result> { let resp = reqwest::get(WEBSITE_URL).await?.json::().await?; - record_current_ip(&resp.origin); + set_ip_file((&resp.origin).as_ref()); Ok(resp.origin) } -/// Stores the current IP value in the FILE_NAME. -/// -/// # Arguments -/// -/// * `current_ip` - A &str holding the current IP value. -fn record_current_ip(current_ip: &str) -> () { - let mut file = File::create(IP_FILE_NAME).expect("Error creating file."); - file.write_all(current_ip.as_ref()) - .expect("Error writing file."); -} - /// Reads the current IP value from the FILE_NAME and returns it. fn check_previous_ip() -> Option { - let path = Path::new(IP_FILE_NAME); - - if !path.exists() { - return None; - } - - let contents = read_to_string(path).expect("Error reading file."); - - Some(contents) + get_ip_file() } diff --git a/src/main.rs b/src/main.rs index 17cfd8e..a5b50bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ use std::env; +use std::error::Error; use log::LevelFilter; use simple_logger::SimpleLogger; -mod go_daddy_ddns; +mod file_handler; +mod records_handler; mod ip_handler; #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), Box> { SimpleLogger::new() .with_colors(true) .with_utc_timestamps() @@ -15,9 +17,11 @@ async fn main() -> Result<(), Box> { .init() .unwrap(); + file_handler::application_folder_setup().expect("Error setting up application folder."); + let domain = env::var("DOMAIN").expect("You need to set DOMAIN env variable first."); let key = env::var("KEY").expect("You need to set KEY env variable first."); let secret = env::var("SECRET").expect("You need to set SECRET env variable first."); - go_daddy_ddns::exec(&domain, &key, &secret).await + records_handler::update(&domain, &key, &secret).await } diff --git a/src/go_daddy_ddns/dns_record.rs b/src/records_handler/dns_record.rs similarity index 100% rename from src/go_daddy_ddns/dns_record.rs rename to src/records_handler/dns_record.rs diff --git a/src/go_daddy_ddns/mod.rs b/src/records_handler/mod.rs similarity index 88% rename from src/go_daddy_ddns/mod.rs rename to src/records_handler/mod.rs index 33aec2c..276ce0d 100644 --- a/src/go_daddy_ddns/mod.rs +++ b/src/records_handler/mod.rs @@ -1,17 +1,14 @@ use std::collections::HashMap; -use std::fs::read_to_string; -use std::path::Path; use log::{debug, info}; use strfmt::strfmt; -use crate::go_daddy_ddns::dns_record::{DNSRecord, DNSRecordsHolder}; +use crate::file_handler::get_records_file; +use crate::records_handler::dns_record::{DNSRecord, DNSRecordsHolder}; use crate::ip_handler::get_ip_to_publish; mod dns_record; -const RECORDS_FILE_NAME: &'static str = "records.json"; - /// Updates the DNS records if the IP has changed and returns the result of the execution. /// /// # Arguments @@ -21,7 +18,9 @@ const RECORDS_FILE_NAME: &'static str = "records.json"; /// * `key` - A &str holding the GoDaddy developer key. /// /// * `secret` - A &str holding the GoDaddy developer secret. -pub async fn exec(domain: &str, key: &str, secret: &str) -> Result<(), Box> { +pub async fn update(domain: &str, key: &str, secret: &str) -> Result<(), Box> { + let records = get_records(); + info!("Checking if the IP has changed."); let new_ip = get_ip_to_publish().await; @@ -32,7 +31,7 @@ pub async fn exec(domain: &str, key: &str, secret: &str) -> Result<(), Box Result<(), Box Vec { - let path = Path::new(RECORDS_FILE_NAME); - let content = read_to_string(path).unwrap(); + let content = get_records_file(); let base: DNSRecordsHolder = serde_json::from_str(&content).expect("Failed to deserialize JSON");