diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e3b0bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +db.sqlite* +output.log +properties.json \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..cba1020 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Cargo launch", + "cargo": { + "args": [ + "build", + "--manifest-path", + "server/Cargo.toml" + ] + }, + "program": "${cargo:program}", + "args": [] + } + ] +} \ No newline at end of file diff --git a/server/src/logger.rs b/server/src/logger.rs index ebcbf0f..22846f0 100644 --- a/server/src/logger.rs +++ b/server/src/logger.rs @@ -10,47 +10,42 @@ use crossterm::{ terminal::{Clear, ClearType}, }; -use fern::colors::{ColoredLevelConfig, Color}; -pub fn setup() -> Result<(Close, Stdin), fern::InitError> { - let (stdout, stdin, close, join_handle) = TerminalHandler::new(|x| { - if x == "sv_cheats" { - log::info!("CHEATS ENABLED") - } - x - }); - let colors_line = ColoredLevelConfig::new() - .error(Color::Red) - .warn(Color::Yellow) - // we actually don't need to specify the color for debug and info, they are white by default - .info(Color::White) - .debug(Color::BrightBlack) - // depending on the terminals color scheme, this is the same as the background color - .trace(Color::BrightBlack); - let colors_level = colors_line.clone().info(Color::Green).debug(Color::BrightBlack); - let stdout_logger = fern::Dispatch::new() - .format(move |out, message, record| { - out.finish(format_args!( - "{colors_line}{}[{}{colors_line}][{}] {}\x1B[0m", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), - colors_level.clone().color(record.level()), - record.target(), - message, - colors_line = format!("\x1B[{}m", colors_line.clone().get_color(&record.level()).to_fg_str()), - // colors_level = colors_level.clone().get_color(&record.level()).to_fg_str(), - )) - }) - .level(log::LevelFilter::Debug) +use fern::colors::{Color, ColoredLevelConfig}; + +mod color_message; +use color_message::print_line; + +// TODO: Cleanup + +pub fn setup( + properties: &crate::server_properties::ServerProperties, +) -> Result<(Close, Stdin), fern::InitError> { + let (stdout, stdin, close, join_handle) = TerminalHandler::new( + |x| { + if x == "sv_cheats" { + log::info!("CHEATS ENABLED") + } + x + }, + properties.use_colors, + ); + + // let simple_formatter = |out: fern::FormatCallback, message: _, record: &log::Record| { + // out.finish(format_args!( + // "{}[{}][{}] {}", + // chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + // record.level(), + // record.target(), + // message, + // )) + // }; + let stdout_logger = if properties.use_colors { + colored_formatter(fern::Dispatch::new()) + } else { + simple_formatter(fern::Dispatch::new()) + } .chain(stdout); - let file = fern::Dispatch::new().format(|out, message, record| { - out.finish(format_args!( - "{}[{}][{}] {}", - chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), - record.level(), - record.target(), - message, - )) - }) - .chain( + let file = simple_formatter(fern::Dispatch::new()).chain( std::fs::OpenOptions::new() .write(true) .create(true) @@ -59,16 +54,67 @@ pub fn setup() -> Result<(Close, Stdin), fern::InitError> { ); fern::Dispatch::new() + .level(log::LevelFilter::Debug) .level_for("h2::codec", log::LevelFilter::Info) .level_for("tokio::codec", log::LevelFilter::Info) .level_for("sqlx::query", log::LevelFilter::Info) .chain(stdout_logger) .chain(file) .apply()?; - log::warn!("TEST1"); - log::error!("TEST2"); + log::info!("Saving output to output.log"); // std::process::exit(1); - Ok((Close{close, handle: join_handle}, stdin)) + Ok(( + Close { + close, + handle: join_handle, + }, + stdin, + )) +} + +fn simple_formatter(d: fern::Dispatch) -> fern::Dispatch { + d.format( + |out: fern::FormatCallback, message: _, record: &log::Record| { + out.finish(format_args!( + "{}[{}][{}] {}", + chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + record.level(), + record.target(), + message, + )) + }, + ) +} + +fn colored_formatter(d: fern::Dispatch) -> fern::Dispatch { + let colors_line = ColoredLevelConfig::new() + .error(Color::Red) + .warn(Color::Yellow) + // we actually don't need to specify the color for debug and info, they are white by default + .info(Color::White) + .debug(Color::BrightBlack) + // depending on the terminals color scheme, this is the same as the background color + .trace(Color::BrightBlack); + let colors_level = colors_line + .clone() + .info(Color::Green) + .debug(Color::BrightBlack); + d.format( + move |out: fern::FormatCallback, message: _, record: &log::Record| { + out.finish(format_args!( + "{colors_line}{}[{}{colors_line}][{}] {}\x1B[0m", + chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + colors_level.clone().color(record.level()), + record.target(), + message, + colors_line = format!( + "\x1B[{}m", + colors_line.clone().get_color(&record.level()).to_fg_str() + ), + // colors_level = colors_level.clone().get_color(&record.level()).to_fg_str(), + )) + }, + ) } pub type Stdout = mpsc::Sender; @@ -83,8 +129,9 @@ where stdin: mpsc::Sender, close: mpsc::Receiver<()>, handler: F, - lines: LimitedVec, - command: String, + lines: LimitedVec, + command: String, + use_colors: bool, } impl TerminalHandler @@ -94,6 +141,7 @@ where { fn new( handler: F, + use_colors: bool, ) -> ( Stdout, Stdin, @@ -112,17 +160,16 @@ where stdin: stdin_send, close: close_recv, handler, - lines: LimitedVec::new(crossterm::terminal::size().unwrap().1 as usize - 1), - command: String::new(), + lines: LimitedVec::new(crossterm::terminal::size().unwrap().1 as usize - 1), + command: String::new(), + use_colors, }; loop { if s.iterate() { break; } } - queue!(stdout, crossterm::event::DisableMouseCapture).unwrap(); - crossterm::terminal::disable_raw_mode().ok(); - println!("Closing printing thread"); + cleanup(); }); (stdout_send, stdin_recv, close_send, join_handle) } @@ -132,46 +179,42 @@ where match self.close.try_recv() { Err(mpsc::TryRecvError::Empty) => (), _ => return true, - } + } while is_event_available() { match crossterm::event::read().unwrap() { Event::Resize(_cols, rows) => self.lines.size = rows as usize - 1, Event::Key(k) => match (k.code, k.modifiers) { (KeyCode::Char('c'), KeyModifiers::CONTROL) => { - let mut stdout = std::io::stdout(); - queue!(stdout, crossterm::event::DisableMouseCapture).unwrap(); - crossterm::terminal::disable_raw_mode().unwrap(); - stdout.flush().unwrap(); - println!("Exiting server"); + cleanup(); std::process::exit(0) } - (k, _m) => { - match k { - KeyCode::Char(c) => self.command.push(c), - KeyCode::Backspace => {self.command.pop();}, - KeyCode::Enter => { - log::info!(target: "COMMAND", "{}", self.command); - match self.stdin.send((self.handler)(self.command.clone())) { - Ok(_) => (), - Err(e) => log::error!("{}", e), - }; - self.command = String::new(); - } - KeyCode::Esc => { - self.lines.scroll_to_bottom(); - } - _ => () - } - }, + (k, _m) => match k { + KeyCode::Char(c) => self.command.push(c), + KeyCode::Backspace => { + self.command.pop(); + } + KeyCode::Enter => { + log::info!(target: "command", " >> {}", self.command); + match self.stdin.send((self.handler)(self.command.clone())) { + Ok(_) => (), + Err(e) => log::error!("{}", e), + }; + self.command = String::new(); + } + KeyCode::Esc => { + self.lines.scroll_to_bottom(); + } + _ => (), + }, }, Event::Mouse(m) => match m { MouseEvent::ScrollUp(_, _, _) => self.lines.scroll_up(), MouseEvent::ScrollDown(_, _, _) => self.lines.scroll_down(), _ => (), - }, - } - - updated = true; + }, + } + + updated = true; } while let Some(x) = match self.stdout.try_recv() { Err(mpsc::TryRecvError::Empty) => None, @@ -190,18 +233,44 @@ where // println!("Got stdout"); if updated { queue!(stdout, Clear(ClearType::All), MoveTo(0, 0)).unwrap(); - for v in self.lines.iter() { - queue!(stdout, Print(v), MoveToNextLine(1)).unwrap(); + let mut i = 0; + let size = crossterm::terminal::size().unwrap(); + for v in self.lines.iter(self.use_colors) { + print_line(&v, self.use_colors, &mut stdout); + queue!(stdout, MoveToNextLine(1)).unwrap(); + i += 1; + if i >= size.1 as usize - 1 { + break; + } // println!("{}", v); - } - let size = crossterm::terminal::size().unwrap(); - queue!(stdout, MoveTo(0, size.1), Clear(ClearType::CurrentLine), Print(&self.command)).unwrap(); + } + queue!( + stdout, + MoveTo(0, size.1), + // Clear(ClearType::CurrentLine), + Print(&format!(" >> {}", self.command)) + ) + .unwrap(); stdout.flush().unwrap(); } false } } +fn cleanup() { + let mut stdout = std::io::stdout(); + queue!( + stdout, + Clear(ClearType::All), + MoveTo(0, 0), + crossterm::event::DisableMouseCapture + ) + .unwrap(); + crossterm::terminal::disable_raw_mode().unwrap(); + stdout.flush().unwrap(); + println!("Exiting server, output has been saved to output.log"); +} + impl Drop for TerminalHandler where T: Send + 'static, @@ -218,15 +287,15 @@ fn is_event_available() -> bool { } pub struct Close { - close: mpsc::Sender<()>, - handle: std::thread::JoinHandle<()>, + close: mpsc::Sender<()>, + handle: std::thread::JoinHandle<()>, } impl Close { pub fn close(self) { - self.close.send(()).unwrap(); - self.handle.join().unwrap(); - println!("CLOSING"); + self.close.send(()).unwrap(); + self.handle.join().unwrap(); + println!("CLOSING"); queue!(std::io::stdout(), crossterm::event::DisableMouseCapture).unwrap(); } } @@ -249,15 +318,18 @@ impl LimitedVec { fn push(&mut self, v: T) { // while self.inner.len() >= self.size { // self.inner.remove(0); - // } - if self.pos != 0 { - self.pos += 1; - } + // } + if self.pos != 0 { + self.pos += 1; + } self.inner.push(v); // assert!(self.inner.len() <= self.size) } - fn iter(&self) -> std::slice::Iter { + fn iter<'a>(&'a self, use_colors: bool) -> Box + 'a> + where + T: std::fmt::Display, + { let start = if self.inner.len() < self.size { 0 } else { @@ -271,7 +343,24 @@ impl LimitedVec { if end < self.inner.len() { end = self.inner.len() } - self.inner[start..end].iter() + let iter = self.inner[start..end].iter(); + + let map = iter.enumerate().map(move |(i, x)| { + format!( + "{}{}", + if use_colors { + format!( + "\x1B[{}m{:>5}\x1B[0m ", + fern::colors::Color::BrightBlack.to_fg_str(), + start + i + ) + } else { + String::new() + }, + x + ) + }); + Box::new(map) } fn scroll_up(&mut self) { diff --git a/server/src/logger/color_message.rs b/server/src/logger/color_message.rs new file mode 100644 index 0000000..70973d7 --- /dev/null +++ b/server/src/logger/color_message.rs @@ -0,0 +1,36 @@ +use crossterm::{ + cursor::{MoveRight, MoveToNextLine}, + event::{Event, KeyCode, KeyModifiers, MouseEvent}, + queue, + style::{Print, PrintStyledContent}, +}; + +use std::io::Write; + +pub fn print_line(message: &String, use_colors: bool, stdout: &mut std::io::Stdout) { + if use_colors { + let mut s = String::new(); + let mut highlight = false; + for c in message.chars() { + + if c == '`' { + // queue!(stdout, Print(format!("{}`", highlight))).unwrap(); + if highlight { + let styled = format!("\x1B[{}m{}\x1B[0m", fern::colors::Color::Blue.to_fg_str(), s); + queue!(stdout, Print(styled)).unwrap(); + s = String::new(); + }else{ + queue!(stdout, Print(s)).unwrap(); + s = String::new(); + } + highlight = !highlight; + continue; + } + + s.push(c); + } + queue!(stdout, Print(&s)).unwrap(); + }else { + queue!(stdout, Print(message)).unwrap(); + } +} diff --git a/server/src/main.rs b/server/src/main.rs index dd91764..12f6d10 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -4,31 +4,29 @@ mod games; mod logger; mod server_properties; + +use log::info; + #[tokio::main] async fn main() { server_properties::setup(); let p = server_properties::ServerProperties::load(); - let (close, _stdin) = logger::setup().unwrap(); - // protobuf::route_guide_grpc::RouteGuid - // for i in 0..100 { - // std::thread::sleep(std::time::Duration::from_secs_f32(0.1)); + let (close, _stdin) = logger::setup(&p).unwrap(); + info!(target: "setup", "Server name: `{}`", p.name); + info!(target: "setup", "Serving on address `{}`", p.addr); + // for i in 0..1000 { + // std::thread::sleep(std::time::Duration::from_secs_f32(1.)); - // if i == 69 { - // log::info!("69 Nͬ͂ͧi̽̅̽҉̫̕͢ĉ͆̚҉̢͞͏eͫ̑͑"); - // std::thread::sleep(std::time::Duration::from_secs_f32(0.2)); - // }else{ - // log::info!("{}", i); - // } + // info!("{}\nTEXTETX\nTEXT", i) // } - + // std::thread::sleep(std::time::Duration::from_secs_f32(200.)); + info!(target: "setup", "Loading games from the games directory"); let games = games::load_games(); log::info!("{:?}", games); games[0].run(4); + info!(target: "setup", "Loading database"); let pool = db::DbPool::new().await; - // let mut conn = pool.acquire().await; - // println!("{}", conn.add_user("Hi").await); - // println!("{:?}", conn.users().await); - // conn.close().await; + info!(target: "setup", "Starting server"); grpc::start(pool, games, p).await; close.close(); }