diff --git a/src/file_writer.rs b/src/file_writer.rs index 17d5b0a..3752f16 100644 --- a/src/file_writer.rs +++ b/src/file_writer.rs @@ -1,27 +1,31 @@ -use std::io::prelude::*; -use std::fs::OpenOptions; -use std::path::Path; -use std::fs::File; - -pub fn write_to_file(text: String, file_path: &str) -> Result<(), String> { +use async_std::fs::OpenOptions; +use async_std::path::Path; +use async_std::fs::File; +use async_std::prelude::*; +pub async fn write_to_file(text: String, file_path: &str) -> Result<(), String> { let mut file = OpenOptions::new() .append(true) .create(true) .open(file_path) + .await .map_err(|e| e.to_string())?; - write!(file, "{} ", text).map_err(|e| e.to_string()) + file.write_all(text.as_bytes()).await.map_err(|e| e.to_string()) } -pub fn update_credentials(username: String, password: String) { +pub async fn update_credentials(username: String, password: String) -> Result<(), String> { let file_path = "config/smtp_account.txt"; let path = Path::new(file_path); let mut content = String::new(); { - let mut file = File::open(path).expect("An error occurs when tried to open a file"); - file.read_to_string(&mut content).expect("An error occurs when tried to read the file"); + let mut file = File::open(path) + .await + .map_err(|e| e.to_string())?; + file.read_to_string(&mut content) + .await + .map_err(|e| e.to_string())?; } let updated_content = content @@ -29,7 +33,16 @@ pub fn update_credentials(username: String, password: String) { .replace("password:password123123", &format!("password:{}", password)); { - let mut file = OpenOptions::new().write(true).truncate(true).open(&path).expect("An error occurs when tried to open a file with OpenOptions"); - file.write_all(updated_content.as_bytes()).expect("An error occurs when tried to update content"); + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open(&path) + .await + .map_err(|e| e.to_string())?; + file.write_all(updated_content.as_bytes()) + .await + .map_err(|e| e.to_string())?; } + + Ok(()) } diff --git a/src/mail.rs b/src/mail.rs index 17cf21f..77600e3 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -5,7 +5,7 @@ use lettre::{ use crate::file_reader::smtp_account_reader; use crate::file_writer::write_to_file; -pub fn mail_sender(title: &str, text: String) { +pub async fn mail_sender(title: &str, text: String) { let email = Message::builder() .from("ServerAPI ".parse().expect("Error parsing sender in Message Builder")) // .reply_to("Yuin ".parse().unwrap()) @@ -28,14 +28,14 @@ pub fn mail_sender(title: &str, text: String) { // Send the email match mailer.send(&email) { Ok(_) => { - match write_to_file(format!("Email has been sent successfully!"), "config/mail_logs.txt") { + match write_to_file(format!("Email has been sent successfully!"), "config/mail_logs.txt").await { Ok(()) => {}, n => println!("An error occured when tried to write logs (email success): {n:?}") } println!("Email has been sent successfully!"); }, Err(e) => { - match write_to_file(format!("Could not send email: {e:?}"), "config/mail_logs.txt") { + match write_to_file(format!("Could not send email: {e:?}"), "config/mail_logs.txt").await { Ok(()) => {}, n => println!("An error occured when tried to write logs (email failure): {n:?}") } diff --git a/src/main.rs b/src/main.rs index a12a720..62164d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,13 @@ mod mail; mod file_reader; mod file_writer; mod scheduler; -mod mc_server; +// mod mc_server; mod setup; +#[cfg(test)] +mod tests; use scheduler::status_upload; -use mc_server::start_mc_server; +// use mc_server::start_mc_server; use file_writer::write_to_file; use setup::check_config_components; use std::thread; @@ -15,13 +17,13 @@ use tracing_subscriber; fn main() { tracing_subscriber::fmt::init(); - let setup_handle = thread::Builder::new().name("setup".to_string()).spawn(|| { + let setup_handle = thread::Builder::new().name("setup".to_string()).spawn(|| async { match check_config_components() { Ok(()) => {}, n => println!("An error occured when tried to check config components: {n:?}") } - match write_to_file(format!("All config components are OK, loading the server.."), "config/logs.txt") { + match write_to_file(format!("All config components are OK, loading the server.."), "config/logs.txt").await { Ok(()) => {}, n => println!("An error occured when tried to write logs (setup success): {n:?}") } @@ -29,19 +31,20 @@ fn main() { setup_handle.expect("Unable to join setup handle").join().unwrap(); println!("All config components are OK, loading the server.."); - - let mail_handle = thread::Builder::new().name("mc_server".to_string()).spawn(|| { +/* + let mc_handle = thread::Builder::new().name("mc_server".to_string()).spawn(|| { println!("MC Server has been enabled"); start_mc_server(); println!("MC Server has been disabled"); }); +*/ - let mc_handle = thread::Builder::new().name("mail_server".to_string()).spawn(|| { + let mail_handle = thread::Builder::new().name("mail_server".to_string()).spawn(|| { println!("Mail Server has been enabled"); status_upload(); println!("Mail Server has been disabled"); }); mail_handle.expect("Unable to join mail server handle").join().unwrap(); - mc_handle.expect("Unable to join mc server handle").join().unwrap(); + // mc_handle.expect("Unable to join mc server handle").join().unwrap(); } diff --git a/src/mc_server.rs b/src/mc_server.rs deleted file mode 100644 index 08ee6be..0000000 --- a/src/mc_server.rs +++ /dev/null @@ -1,275 +0,0 @@ -#![allow(clippy::type_complexity)] - -use crate::file_writer::write_to_file; - -use std::collections::VecDeque; -use std::time::{SystemTime, UNIX_EPOCH}; -use rand::prelude::IndexedRandom; - -use rand::Rng; -use valence::prelude::*; -use valence::protocol::sound::{Sound, SoundCategory}; -use valence::spawn::IsFlat; - -const START_POS: BlockPos = BlockPos::new(0, 100, 0); -const VIEW_DIST: u8 = 10; - -const BLOCK_TYPES: [BlockState; 7] = [ - BlockState::GRASS_BLOCK, - BlockState::OAK_LOG, - BlockState::BIRCH_LOG, - BlockState::OAK_LEAVES, - BlockState::BIRCH_LEAVES, - BlockState::DIRT, - BlockState::MOSS_BLOCK, -]; - -pub fn start_mc_server() { - App::new() - .add_plugins(DefaultPlugins) - .add_systems( - Update, - ( - init_clients, - reset_clients.after(init_clients), - manage_chunks.after(reset_clients).before(manage_blocks), - manage_blocks, - despawn_disconnected_clients, - // || println!("") - ), - ) - .run(); -} - -#[derive(Component)] -struct GameState { - blocks: VecDeque, - score: u32, - combo: u32, - target_y: i32, - last_block_timestamp: u128, -} - -fn init_clients( - mut clients: Query< - ( - Entity, - &mut Client, - &mut VisibleChunkLayer, - &mut IsFlat, - &mut GameMode, - ), - Added, - >, - server: Res, - dimensions: Res, - biomes: Res, - mut commands: Commands, -) { - for (entity, mut client, mut visible_chunk_layer, mut is_flat, mut game_mode) in - clients.iter_mut() - { - visible_chunk_layer.0 = entity; - is_flat.0 = true; - *game_mode = GameMode::Adventure; - - client.send_chat_message("Welcome to epic infinite parkour game!".italic()); - match write_to_file(format!("A player has joined the server!"), "config/logs.txt") { - Ok(()) => {}, - n => println!("An error occured when tried to write logs (join): {n:?}") - } - - let state = GameState { - blocks: VecDeque::new(), - score: 0, - combo: 0, - target_y: 0, - last_block_timestamp: 0, - }; - - let layer = ChunkLayer::new(ident!("overworld"), &dimensions, &biomes, &server); - - commands.entity(entity).insert((state, layer)); - } -} - -fn reset_clients( - mut clients: Query<( - &mut Client, - &mut Position, - &mut Look, - &mut GameState, - &mut ChunkLayer, - )>, -) { - for (mut client, mut pos, mut look, mut state, mut layer) in clients.iter_mut() { - let out_of_bounds = (pos.0.y as i32) < START_POS.y - 32; - - if out_of_bounds || state.is_added() { - if out_of_bounds && !state.is_added() { - client.send_chat_message( - "Your score was ".italic() - + state - .score - .to_string() - .color(Color::GOLD) - .bold() - .not_italic(), - ); - match write_to_file(format!("A player fell into the void with the score {}.", state.score), "config/logs.txt") { - Ok(()) => {}, - n => println!("An error occured when tried to write logs (death): {n:?}") - } - } - - // Init chunks. - for pos in ChunkView::new(ChunkPos{ - x: START_POS.x.div_euclid(16), - z: START_POS.z.div_euclid(16), - }, VIEW_DIST).iter() { - layer.insert_chunk(pos, UnloadedChunk::new()); - } - - state.score = 0; - state.combo = 0; - - for block in &state.blocks { - layer.set_block(*block, BlockState::AIR); - } - state.blocks.clear(); - state.blocks.push_back(START_POS); - layer.set_block(START_POS, BlockState::STONE); - - for _ in 0..10 { - generate_next_block(&mut state, &mut layer, false); - } - - pos.set([ - START_POS.x as f64 + 0.5, - START_POS.y as f64 + 1.0, - START_POS.z as f64 + 0.5, - ]); - look.yaw = 0.0; - look.pitch = 0.0; - } - } -} - -fn manage_blocks(mut clients: Query<(&mut Client, &Position, &mut GameState, &mut ChunkLayer)>) { - for (mut client, pos, mut state, mut layer) in clients.iter_mut() { - let pos_under_player = BlockPos::new( - (pos.0.x - 0.5).round() as i32, - pos.0.y as i32 - 1, - (pos.0.z - 0.5).round() as i32, - ); - - if let Some(index) = state - .blocks - .iter() - .position(|block| *block == pos_under_player) - { - if index > 0 { - let power_result = 2.0f32.powf((state.combo as f32) / 45.0); - let max_time_taken = (1000.0f32 * (index as f32) / power_result) as u128; - - let current_time_millis = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(); - - if current_time_millis - state.last_block_timestamp < max_time_taken { - state.combo += index as u32 - } else { - state.combo = 0 - } - - for _ in 0..index { - generate_next_block(&mut state, &mut layer, true) - } - - let pitch = 0.9 + ((state.combo as f32) - 1.0) * 0.05; - client.play_sound( - Sound::BlockNoteBlockBass, - SoundCategory::Master, - pos.0, - 1.0, - pitch, - ); - - client.set_title(""); - client.set_subtitle(state.score.to_string().color(Color::LIGHT_PURPLE).bold()); - } - } - } -} - -fn manage_chunks(mut clients: Query<(&Position, &OldPosition, &mut ChunkLayer), With>) { - for (pos, old_pos, mut layer) in &mut clients { - let old_view = ChunkView::new(ChunkPos{ - x: old_pos.get().x.div_euclid(16f64) as i32, - z: old_pos.get().z.div_euclid(16f64) as i32, - }, VIEW_DIST); - let view = ChunkView::new(ChunkPos{ - x: pos.get().x.div_euclid(16f64) as i32, - z: pos.get().z.div_euclid(16f64) as i32, - }, VIEW_DIST); - - if old_view != view { - for pos in old_view.diff(view) { - layer.remove_chunk(pos); - } - - for pos in view.diff(old_view) { - layer.chunk_entry(pos).or_default(); - } - } - } -} - -fn generate_next_block(state: &mut GameState, layer: &mut ChunkLayer, in_game: bool) { - if in_game { - let removed_block = state.blocks.pop_front().unwrap(); - layer.set_block(removed_block, BlockState::AIR); - - state.score += 1 - } - - let last_pos = *state.blocks.back().unwrap(); - let block_pos = generate_random_block(last_pos, state.target_y); - - if last_pos.y == START_POS.y { - state.target_y = 0 - } else if last_pos.y < START_POS.y - 30 || last_pos.y > START_POS.y + 30 { - state.target_y = START_POS.y; - } - - let mut rng = rand::thread_rng(); - - layer.set_block(block_pos, *BLOCK_TYPES.choose(&mut rng).unwrap()); - state.blocks.push_back(block_pos); - - // Combo System - state.last_block_timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(); -} - -fn generate_random_block(pos: BlockPos, target_y: i32) -> BlockPos { - let mut rng = rand::thread_rng(); - - // if above or below target_y, change y to gradually reach it - let y = match target_y { - 0 => rng.gen_range(-1..2), - y if y > pos.y => 1, - _ => -1, - }; - let z = match y { - 1 => rng.gen_range(1..3), - -1 => rng.gen_range(2..5), - _ => rng.gen_range(1..4), - }; - let x = rng.gen_range(-3..4); - - BlockPos::new(pos.x + x, pos.y + y, pos.z + z) -} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..170fa1d --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,63 @@ +#[cfg(test)] +mod tests { + use crate::*; + use async_std::path::Path; + use async_std::prelude::*; + use async_std::fs::{self, File, OpenOptions}; + use async_std::task; + + #[async_std::test] + async fn test_write_to_file() { + let test_file_path = "test_write_to_file.txt"; + + // Ensure the test file is clean + if Path::new(test_file_path).exists().await { + fs::remove_file(test_file_path).await.expect("Failed to remove test file"); + } + + // Write to the file + write_to_file("Hello, world!".to_string(), test_file_path) + .await + .expect("Failed to write to file"); + + // Read the file content + let mut file = File::open(test_file_path).await.expect("Failed to open test file"); + let mut content = String::new(); + file.read_to_string(&mut content).await.expect("Failed to read test file"); + + // Check the content + assert_eq!(content, "Hello, world!"); + + // Clean up + fs::remove_file(test_file_path).await.expect("Failed to remove test file"); + } + + #[async_std::test] + async fn test_update_credentials() { + let test_file_path = "config/test_smtp_account.txt"; + + // Ensure the config directory exists + fs::create_dir_all("config").await.expect("Failed to create config directory"); + + // Prepare the test file + let initial_content = "username:example@gmail.com\npassword:password123123\n"; + fs::write(test_file_path, initial_content).await.expect("Failed to write initial content"); + + // Update credentials + update_credentials("new_username".to_string(), "new_password".to_string()) + .await + .expect("Failed to update credentials"); + + // Read the updated file content + let mut file = File::open(test_file_path).await.expect("Failed to open test file"); + let mut content = String::new(); + file.read_to_string(&mut content).await.expect("Failed to read test file"); + + // Check the updated content + let expected_content = "username:new_username\npassword:new_password\n"; + assert_eq!(content, expected_content); + + // Clean up + fs::remove_file(test_file_path).await.expect("Failed to remove test file"); + } +}