From 2571ce57564f2a193933ffae43fad9c56d49afa4 Mon Sep 17 00:00:00 2001 From: Solomon Wagner Date: Fri, 17 May 2024 14:46:41 -0400 Subject: [PATCH] Created Boardersv2 moved old to Boardersv1 --- .gitignore | 8 - src/main.rs | 447 ---------------- v1/.gitignore | 6 + Cargo.toml => v1/Cargo.toml | 0 {compat => v1/compat}/2.2.0.sh | 0 overflow.tar.gz => v1/overflow.tar.gz | Bin {src => v1/src}/api/boards.rs | 0 {src => v1/src}/api/messages.rs | 0 {src => v1/src}/api/mod.rs | 0 {src => v1/src}/api/types.rs | 0 {src => v1/src}/api/users.rs | 0 {src => v1/src}/bin/boarders_client.rs | 0 {src => v1/src}/html/accounts.rs | 0 {src => v1/src}/html/boards.rs | 0 {src => v1/src}/html/mod.rs | 0 {src => v1/src}/lib/boards.rs | 0 {src => v1/src}/lib/messages.rs | 0 {src => v1/src}/lib/mod.rs | 0 {src => v1/src}/lib/users.rs | 0 v1/src/main.rs | 480 ++++++++++++++++++ {src => v1/src}/res/auth.hbs | 0 {src => v1/src}/res/banner | 0 {src => v1/src}/res/board.hbs | 0 {src => v1/src}/res/board_index.hbs | 0 {src => v1/src}/res/new_account_redirect.hbs | 0 {src => v1/src}/res/snippets/board_bar.hbs | 0 {src => v1/src}/res/snippets/head.hbs | 0 {src => v1/src}/res/snippets/login_status.hbs | 0 {src => v1/src}/res/welcome.hbs | 0 {static => v1/static}/account_login.html | 0 {static => v1/static}/index.css | 0 {static => v1/static}/navbar.html | 0 {static => v1/static}/new_account.html | 0 {static => v1/static}/new_board.html | 0 v2/.gitignore | 1 + v2/go.mod | 3 + v2/main.go | 9 + 37 files changed, 499 insertions(+), 455 deletions(-) delete mode 100644 .gitignore delete mode 100644 src/main.rs create mode 100644 v1/.gitignore rename Cargo.toml => v1/Cargo.toml (100%) rename {compat => v1/compat}/2.2.0.sh (100%) rename overflow.tar.gz => v1/overflow.tar.gz (100%) rename {src => v1/src}/api/boards.rs (100%) rename {src => v1/src}/api/messages.rs (100%) rename {src => v1/src}/api/mod.rs (100%) rename {src => v1/src}/api/types.rs (100%) rename {src => v1/src}/api/users.rs (100%) rename {src => v1/src}/bin/boarders_client.rs (100%) rename {src => v1/src}/html/accounts.rs (100%) rename {src => v1/src}/html/boards.rs (100%) rename {src => v1/src}/html/mod.rs (100%) rename {src => v1/src}/lib/boards.rs (100%) rename {src => v1/src}/lib/messages.rs (100%) rename {src => v1/src}/lib/mod.rs (100%) rename {src => v1/src}/lib/users.rs (100%) create mode 100644 v1/src/main.rs rename {src => v1/src}/res/auth.hbs (100%) rename {src => v1/src}/res/banner (100%) rename {src => v1/src}/res/board.hbs (100%) rename {src => v1/src}/res/board_index.hbs (100%) rename {src => v1/src}/res/new_account_redirect.hbs (100%) rename {src => v1/src}/res/snippets/board_bar.hbs (100%) rename {src => v1/src}/res/snippets/head.hbs (100%) rename {src => v1/src}/res/snippets/login_status.hbs (100%) rename {src => v1/src}/res/welcome.hbs (100%) rename {static => v1/static}/account_login.html (100%) rename {static => v1/static}/index.css (100%) rename {static => v1/static}/navbar.html (100%) rename {static => v1/static}/new_account.html (100%) rename {static => v1/static}/new_board.html (100%) create mode 100644 v2/.gitignore create mode 100644 v2/go.mod create mode 100644 v2/main.go diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 9991d51..0000000 --- a/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ - -/target - -# new data directory, specified in config gile -# NOTE: this config file does not yet exist -/data - -Cargo.lock diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 908f9a4..0000000 --- a/src/main.rs +++ /dev/null @@ -1,447 +0,0 @@ -use std::{ - env, fs, io, - io::{Write, Read}, - sync::Mutex, - fs::ReadDir, - path::Path, - error::Error, - process::Command -}; - -use actix_web::{ - get, web, - App, HttpResponse, HttpServer, Responder, -}; - -mod lib; -use lib::{ - *, - Archiveable, - boards::{ - Board, BoardFactory - }, - users::UserManager, - messages::MessageFactory -}; - -mod api; -use api::types::AppState; - -mod html; -use html::{ - accounts::*, - boards::* -}; - -use tokio::{ - sync::{oneshot, watch}, - net::{ - UnixStream, UnixListener, - }, - runtime::Runtime, - select -}; - -use handlebars::{Handlebars, handlebars_helper}; -use console::style; -use maplit::btreemap; - -use simple_logger::{SimpleLogger}; -use log::LevelFilter; - -use serde::{Serialize, Deserialize}; - -pub struct ThreadData<'a> { - handlebars: Handlebars<'a> -} - -#[get("/")] -async fn index( - tdata: web::Data> -) -> impl Responder { - HttpResponse::Ok().body( - tdata.handlebars.render("welcome", &btreemap!( - "version" => env!("CARGO_PKG_VERSION").to_string() - )).unwrap() - ) -} - -#[get("/debug/")] -async fn debug( - data: web::Data -) -> impl Responder { - HttpResponse::Ok().body(serde_json::to_string_pretty(data.as_ref()).unwrap()) -} - -async fn socket_loop(socket: &UnixStream) -> Result<(), Box> { - loop { - let mut data = vec![0; 1024]; - - socket.readable().await?; - let mut command = String::new(); - loop { - match socket.try_read(&mut data) { - Ok(0) => break, - Ok(_n) => { - command.push_str(&String::from_utf8(data.clone()).unwrap().trim()); - println!("command: {}", command); - }, - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - break; - }, - Err(e) => { - return Err(e.into()); - } - } - } - - socket.writable().await?; - match socket.try_write(&command.into_bytes()) { - Ok(_n) => { - }, - Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { - continue; - }, - Err(e) => { - return Err(e.into()); - } - } - } -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -struct AppInfo<'s> { - version: &'s str, -} - -impl<'s> AppInfo<'s> { - fn get_version(&self) -> Option<[u8; 3]> { - let mut a: [u8; 3] = [0; 3]; - let mut ver = self.version.split('.'); - a[0] = ver.next()?.parse().ok()?; - a[1] = ver.next()?.parse().ok()?; - a[2] = ver.next()?.parse().ok()?; - Some(a) - } -} - -static mut app_info: AppInfo = AppInfo { - version: env!("CARGO_PKG_VERSION") -}; - -#[actix_web::main] -async fn main() -> io::Result<()> { - let args: Vec = env::args().collect(); - let mut args = args.iter(); - args.next().unwrap(); - for arg in args { - match arg.as_str() { - "-v" | "--version" => { - println!("Boarders v{}", unsafe { app_info.version }); - std::process::exit(0); - } - _ => { - println!("Unknown argument \"{}\"", arg); - } - } - } - - /* quick and dirty logging */ - SimpleLogger::new() - .with_module_level("mio::poll", LevelFilter::Off) - .with_module_level("actix_server::worker", LevelFilter::Debug) - .with_module_level("handlebars", LevelFilter::Info) - .init().unwrap(); - - println!("Initializing..."); - /* - * TODO: config files - * TODO: allow host to specify the data dir via a config file - * why? why not? maybe you want multiple servers running on - * the same machine - * TODO: allow host to specify config file via cmd line (overrides default name) - */ - let data_dir = Path::new("data/"); - let mut pwd = env::current_dir().unwrap(); - - /* Getting app version and checking for backwards compatability issues */ - let mut app_info_file = data_dir.to_path_buf(); - app_info_file.push("app.json"); - let mut app_info_file = fs::OpenOptions::new().read(true).write(true).create(true).open(app_info_file).unwrap(); - let mut app_info_str = String::new(); - app_info_file.read_to_string(&mut app_info_str).unwrap(); - let cur_app_info: AppInfo = match serde_json::from_str(&app_info_str) { - Ok(a) => a, - Err(_) => { - println!("Malformed app.json file, backing up then creating new"); - let s = unsafe { serde_json::to_string(&app_info).unwrap() }; - app_info_file.write_all(s.as_bytes()).unwrap(); - unsafe { app_info.clone() } - } - }; - - let ver = cur_app_info.get_version().unwrap(); - match ver[0] { - x if x <= 2 => match ver[1] { - y if y <= 2 => match ver[2] { - 0 => { - if Command::new("./compat/2.2.0.sh").output().is_err() { - println!("Error, could not complete compatability upgrade"); - } else { - println!("Fixed compat issues for v2.2.0"); - } - } - _ => {println!("Invalid version number?")} - } - _ => {println!("Invalid version number?")} - } - _ => {println!("Invalid version number?")} - } - - /* open/create the directory where board data is placed */ - let board_files: ReadDir = fs::read_dir({ - pwd.push(data_dir); - pwd.push("boards/"); - &pwd - }).unwrap_or({ - fs::create_dir_all(&pwd).unwrap(); - fs::read_dir(pwd).unwrap() - }); - - let mut boards: Vec = Vec::new(); - - /* - * Iterate over all the files inside the board data dir and - * open/create them. Will panic! if there is an error opening - * a board - */ - for file in board_files.map(|x| x.unwrap()) { - if file.file_type().unwrap().is_dir() { - let mut path = file.path().to_path_buf(); - path.push("board.json"); - println!("loading board: {:?}", path); - boards.push( - Board::open(&path).unwrap_or_else(|e| { - panic!("Could not open board {:?}\n{}", path, e) - }) - ); - } - } - - /* sort boards by id so they display correctly on the board index */ - boards.sort_by(|a,b| a.id.cmp(&b.id)); - println!("{}", style(format!("Found and loaded {} boards!", boards.len())).green()); - - let mut board_data = std::path::PathBuf::new(); - board_data.push(&data_dir); - let ff = FactoryFactory::new(data_dir); - let (mut bf, mf, um) = ff.build(); - board_data.push("boards/"); - bf.set_data_dir(board_data); - - /* Initialize all the the shared data crap. copy/pasting code time!! */ - let web_data = web::Data::new(AppState { - boards: Mutex::new(boards.clone()), - board_factory: Mutex::new(BoardFactory::load_state({ - println!("Loading board factory file..."); - let mut dd = data_dir.to_path_buf(); - dd.push("boards/factory.json"); - dd - }).unwrap_or(bf)), - msg_factory: Mutex::new(MessageFactory::load_state({ - println!("Loading message factory file..."); - let mut dd = data_dir.to_path_buf(); - dd.push("message_factory.json"); - dd - }).unwrap_or(mf)), - user_manager: Mutex::new(UserManager::load_state({ - println!("Loading user manager file..."); - let mut dd = data_dir.to_path_buf(); - dd.push("users.json"); - dd - }).unwrap_or(um)) - }); - let wb = web_data.clone(); - println!("{}", style("Loaded/Created application data files!").green()); - - println!("Connecting to Unix socket..."); - let (tx0, rx0) = oneshot::channel::(); - - /* NOTE: More phases will probably exist in the future */ - enum AppPhase { - Normal, - Shutdown - } - - let (s_tx, mut s_rx) = watch::channel::(AppPhase::Normal); - - let rt = Runtime::new().unwrap(); - let _guard = rt.enter(); - /* spawn the thread that manages the unix socket */ - tokio::spawn(async move { - let p = Path::new("./boarders.sock"); - if p.exists() { - std::fs::remove_file(p).unwrap(); - } - let socket = UnixListener::bind("./boarders.sock").unwrap_or_else(|_| { - panic!("{}", style("Could not connect to Unix socket").red()); - }); - println!("{}", style("Listening on Unix socket!").green()); - if tx0.send(true).is_err() { - panic!("{}", style("[FATAL] Thread reciever was dropped! (should never happen)").red().bold()); - } - - /* - * HACK: clone the reciever a bunch of times to get around moves - * not sure if there's a better way - */ - let s_rx_0 = s_rx.clone(); - let spawn_t = tokio::spawn(async move { - let s_rx = s_rx_0.clone(); - loop { - let mut s_rx = s_rx.clone(); - /* accept socket then wait for exit, or shutdown signal */ - let (conn, _addr) = socket.accept().await.unwrap(); - tokio::spawn(async move { - select! { - _socket = socket_loop(&conn) => 0, - _state = s_rx.changed() => 1 - } - }); - } - }); - - /* - * FIXME: use enums when I feel like it (not urgent) - */ - match select! { - _st = spawn_t => 0, - state = s_rx.changed() => if state.is_ok() { - match *s_rx.borrow() { - AppPhase::Shutdown => 1, - _ => 0 - } - } else { - 0 - } - } { - 0 => println!("Client disconnected"), - 1 => println!("Shut down client thread(s)"), - _ => println!("Unexpected value from client thread") - } - }); - - /* why do I want a special message for that? Honestly I don't know... */ - rx0.await.unwrap_or_else( - |_| panic!("{}", style("[FATAL] Thread transmitter was dropped! (should never happen)").red().bold()) - ); - - println!("{}", style("Finished initialization").green().bold()); - println!("Starting server..."); - - let server = HttpServer::new(move || { - App::new() - .data(ThreadData { - handlebars: { - let mut h = Handlebars::new(); - /* - * TODO: allow user to specify wether or not templates should be - * loaded dynamically or statically, probably via environment - * variable at compile time - */ - /* register handlebars partials here */ - handlebars_helper!(msg_helper: |s: str| format!("helped: {}", s.to_string())); - h.register_helper("msg", Box::new(msg_helper)); - h.register_partial("head", include_str!("res/snippets/head.hbs")).unwrap(); - h.register_partial("login_status", include_str!("res/snippets/login_status.hbs")).unwrap(); - h.register_partial("board_bar", include_str!("res/snippets/board_bar.hbs")).unwrap(); - /* register handlebars templates here */ - h.register_template_string("board_index", include_str!("res/board_index.hbs")).unwrap(); - h.register_template_string("welcome", include_str!("res/welcome.hbs")).unwrap(); - h.register_template_string("auth", include_str!("res/auth.hbs")).unwrap(); - h.register_template_string("board", include_str!("res/board.hbs")).unwrap(); - h - } - }).app_data(web_data.clone()) - .service(index) - .service(debug) - .service(list_boards) - .service(login) - .service(auth_html) - .service(sign_up) - .service(sign_up_result) - .service(new_board) - .service(new_board_result) - .service(get_board) - .service(board_post) - .service( - /* add all api services under the /api/ scope */ - web::scope("/api") - .service(api::boards::new) - .service(api::boards::list) - .service(api::messages::new) - .service(api::users::new) - .service(api::users::list) - .service(api::users::get) - .service(api::users::auth)) - /* serve all static files inside ../static/ */ - .service(actix_files::Files::new("/static/", "./static/").show_files_listing()) - }).bind("127.0.0.1:8080")?.run(); - println!("Started server"); - println!("{}v{}", include_str!("res/banner"), env!("CARGO_PKG_VERSION")); - - /* - * Program will block here until it is Ctrl-C 'd on the command line - * TODO: Block until cmd line exit OR client shutdown command - */ - let res = server.await; - - println!("\nExiting..."); - /* - * Note to self: handle all errors as much as possible - * Use unwrap() as little as possible - * If anything goes wrong while saving data data WILL get lost - */ - println!("Saving application state..."); - for b in wb.boards.lock().unwrap().iter() { - fs::create_dir_all(&b.path).unwrap_or_else( - |x| { println!("Could not create directory {:?}: {}", b.path, x) } - ); - b.save_state("board.json").unwrap_or_else( - |x| { println!("Could not save board {:?}: {}", b.title, x) } - ); - } - - wb.board_factory - .lock() - .unwrap() - .save_state("factory.json") - .unwrap_or_else( - |x| { println!("Could not save user factory state: {}", x) } - ); - - wb.msg_factory - .lock() - .unwrap() - .save_state("message_factory.json") - .unwrap_or_else( - |x| { println!("Could not save message factory state: {}", x) } - ); - - wb.user_manager - .lock() - .unwrap() - .save_state("users.json") - .unwrap_or_else( - |x| { println!("Could not save user data: {}", x) } - ); - - /* send shutdown signal to all unix socket threads */ - println!("Shutting down client threads..."); - if s_tx.send(AppPhase::Shutdown).is_err() { - println!("{}", style("Could not update the app phase to Shutdown").red().bold()); - } - rt.shutdown_timeout(std::time::Duration::from_secs(10)); - println!("Done!"); - res -} diff --git a/v1/.gitignore b/v1/.gitignore new file mode 100644 index 0000000..abbb048 --- /dev/null +++ b/v1/.gitignore @@ -0,0 +1,6 @@ + +target + +data + +Cargo.lock diff --git a/Cargo.toml b/v1/Cargo.toml similarity index 100% rename from Cargo.toml rename to v1/Cargo.toml diff --git a/compat/2.2.0.sh b/v1/compat/2.2.0.sh similarity index 100% rename from compat/2.2.0.sh rename to v1/compat/2.2.0.sh diff --git a/overflow.tar.gz b/v1/overflow.tar.gz similarity index 100% rename from overflow.tar.gz rename to v1/overflow.tar.gz diff --git a/src/api/boards.rs b/v1/src/api/boards.rs similarity index 100% rename from src/api/boards.rs rename to v1/src/api/boards.rs diff --git a/src/api/messages.rs b/v1/src/api/messages.rs similarity index 100% rename from src/api/messages.rs rename to v1/src/api/messages.rs diff --git a/src/api/mod.rs b/v1/src/api/mod.rs similarity index 100% rename from src/api/mod.rs rename to v1/src/api/mod.rs diff --git a/src/api/types.rs b/v1/src/api/types.rs similarity index 100% rename from src/api/types.rs rename to v1/src/api/types.rs diff --git a/src/api/users.rs b/v1/src/api/users.rs similarity index 100% rename from src/api/users.rs rename to v1/src/api/users.rs diff --git a/src/bin/boarders_client.rs b/v1/src/bin/boarders_client.rs similarity index 100% rename from src/bin/boarders_client.rs rename to v1/src/bin/boarders_client.rs diff --git a/src/html/accounts.rs b/v1/src/html/accounts.rs similarity index 100% rename from src/html/accounts.rs rename to v1/src/html/accounts.rs diff --git a/src/html/boards.rs b/v1/src/html/boards.rs similarity index 100% rename from src/html/boards.rs rename to v1/src/html/boards.rs diff --git a/src/html/mod.rs b/v1/src/html/mod.rs similarity index 100% rename from src/html/mod.rs rename to v1/src/html/mod.rs diff --git a/src/lib/boards.rs b/v1/src/lib/boards.rs similarity index 100% rename from src/lib/boards.rs rename to v1/src/lib/boards.rs diff --git a/src/lib/messages.rs b/v1/src/lib/messages.rs similarity index 100% rename from src/lib/messages.rs rename to v1/src/lib/messages.rs diff --git a/src/lib/mod.rs b/v1/src/lib/mod.rs similarity index 100% rename from src/lib/mod.rs rename to v1/src/lib/mod.rs diff --git a/src/lib/users.rs b/v1/src/lib/users.rs similarity index 100% rename from src/lib/users.rs rename to v1/src/lib/users.rs diff --git a/v1/src/main.rs b/v1/src/main.rs new file mode 100644 index 0000000..83233ed --- /dev/null +++ b/v1/src/main.rs @@ -0,0 +1,480 @@ +use std::{ + env, + error::Error, + fs, + fs::ReadDir, + io, + io::{Read, Write}, + path::Path, + process::Command, + sync::Mutex, +}; + +use actix_web::{get, web, App, HttpResponse, HttpServer, Responder}; + +mod lib; +use lib::{ + boards::{Board, BoardFactory}, + messages::MessageFactory, + users::UserManager, + Archiveable, *, +}; + +mod api; +use api::types::AppState; + +mod html; +use html::{accounts::*, boards::*}; + +use tokio::{ + net::{UnixListener, UnixStream}, + runtime::Runtime, + select, + sync::{oneshot, watch}, +}; + +use console::style; +use handlebars::{handlebars_helper, Handlebars}; +use maplit::btreemap; + +use log::LevelFilter; +use simple_logger::SimpleLogger; + +use serde::{Deserialize, Serialize}; + +pub struct ThreadData<'a> { + handlebars: Handlebars<'a>, +} + +#[get("/")] +async fn index(tdata: web::Data>) -> impl Responder { + HttpResponse::Ok().body( + tdata + .handlebars + .render( + "welcome", + &btreemap!( + "version" => env!("CARGO_PKG_VERSION").to_string() + ), + ) + .unwrap(), + ) +} + +#[get("/debug/")] +async fn debug(data: web::Data) -> impl Responder { + HttpResponse::Ok().body(serde_json::to_string_pretty(data.as_ref()).unwrap()) +} + +async fn socket_loop(socket: &UnixStream) -> Result<(), Box> { + loop { + let mut data = vec![0; 1024]; + + socket.readable().await?; + let mut command = String::new(); + loop { + match socket.try_read(&mut data) { + Ok(0) => break, + Ok(_n) => { + command.push_str(String::from_utf8(data.clone()).unwrap().trim()); + println!("command: {}", command); + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + break; + } + Err(e) => { + return Err(e.into()); + } + } + } + + socket.writable().await?; + match socket.try_write(&command.into_bytes()) { + Ok(_n) => {} + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + continue; + } + Err(e) => { + return Err(e.into()); + } + } + } +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +struct AppInfo<'s> { + version: &'s str, +} + +impl<'s> AppInfo<'s> { + fn get_version(&self) -> Option<[u8; 3]> { + let mut a: [u8; 3] = [0; 3]; + let mut ver = self.version.split('.'); + a[0] = ver.next()?.parse().ok()?; + a[1] = ver.next()?.parse().ok()?; + a[2] = ver.next()?.parse().ok()?; + Some(a) + } +} + +static mut APP_INFO: AppInfo = AppInfo { + version: env!("CARGO_PKG_VERSION"), +}; + +#[actix_web::main] +async fn main() -> io::Result<()> { + let args: Vec = env::args().collect(); + let mut args = args.iter(); + args.next().unwrap(); + for arg in args { + match arg.as_str() { + "-v" | "--version" => { + println!("Boarders v{}", unsafe { APP_INFO.version }); + std::process::exit(0); + } + _ => { + println!("Unknown argument \"{}\"", arg); + } + } + } + + /* quick and dirty logging */ + SimpleLogger::new() + .with_module_level("mio::poll", LevelFilter::Off) + .with_module_level("actix_server::worker", LevelFilter::Debug) + .with_module_level("handlebars", LevelFilter::Info) + .init() + .unwrap(); + + println!("Initializing..."); + /* + * TODO: config files + * TODO: allow host to specify the data dir via a config file + * why? why not? maybe you want multiple servers running on + * the same machine + * TODO: allow host to specify config file via cmd line (overrides default name) + */ + let data_dir = Path::new("data/"); + let mut pwd = env::current_dir().unwrap(); + + /* Getting app version and checking for backwards compatability issues */ + let mut app_info_file = data_dir.to_path_buf(); + app_info_file.push("app.json"); + let mut app_info_file = fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(app_info_file) + .unwrap(); + let mut app_info_str = String::new(); + app_info_file.read_to_string(&mut app_info_str).unwrap(); + let cur_app_info: AppInfo = match serde_json::from_str(&app_info_str) { + Ok(a) => a, + Err(_) => { + println!("Malformed app.json file, backing up then creating new"); + let s = unsafe { serde_json::to_string(&APP_INFO).unwrap() }; + app_info_file.write_all(s.as_bytes()).unwrap(); + unsafe { APP_INFO.clone() } + } + }; + + let ver = cur_app_info.get_version().unwrap(); + match ver[0] { + x if x <= 2 => match ver[1] { + y if y <= 2 => match ver[2] { + 0 => { + if Command::new("./compat/2.2.0.sh").output().is_err() { + println!("Error, could not complete compatability upgrade"); + } else { + println!("Fixed compat issues for v2.2.0"); + } + } + _ => { + println!("Invalid version number?") + } + }, + _ => { + println!("Invalid version number?") + } + }, + _ => { + println!("Invalid version number?") + } + } + + /* open/create the directory where board data is placed */ + let board_files: ReadDir = fs::read_dir({ + pwd.push(data_dir); + pwd.push("boards/"); + &pwd + }) + .unwrap_or({ + fs::create_dir_all(&pwd).unwrap(); + fs::read_dir(pwd).unwrap() + }); + + let mut boards: Vec = Vec::new(); + + /* + * Iterate over all the files inside the board data dir and + * open/create them. Will panic! if there is an error opening + * a board + */ + for file in board_files.map(|x| x.unwrap()) { + if file.file_type().unwrap().is_dir() { + let mut path = file.path().to_path_buf(); + path.push("board.json"); + println!("loading board: {:?}", path); + boards.push( + Board::open(&path) + .unwrap_or_else(|e| panic!("Could not open board {:?}\n{}", path, e)), + ); + } + } + + /* sort boards by id so they display correctly on the board index */ + boards.sort_by(|a, b| a.id.cmp(&b.id)); + println!( + "{}", + style(format!("Found and loaded {} boards!", boards.len())).green() + ); + + let mut board_data = std::path::PathBuf::new(); + board_data.push(data_dir); + let ff = FactoryFactory::new(data_dir); + let (mut bf, mf, um) = ff.build(); + board_data.push("boards/"); + bf.set_data_dir(board_data); + + /* Initialize all the the shared data crap. copy/pasting code time!! */ + let web_data = web::Data::new(AppState { + boards: Mutex::new(boards.clone()), + board_factory: Mutex::new( + BoardFactory::load_state({ + println!("Loading board factory file..."); + let mut dd = data_dir.to_path_buf(); + dd.push("boards/factory.json"); + dd + }) + .unwrap_or(bf), + ), + msg_factory: Mutex::new( + MessageFactory::load_state({ + println!("Loading message factory file..."); + let mut dd = data_dir.to_path_buf(); + dd.push("message_factory.json"); + dd + }) + .unwrap_or(mf), + ), + user_manager: Mutex::new( + UserManager::load_state({ + println!("Loading user manager file..."); + let mut dd = data_dir.to_path_buf(); + dd.push("users.json"); + dd + }) + .unwrap_or(um), + ), + }); + let wb = web_data.clone(); + println!( + "{}", + style("Loaded/Created application data files!").green() + ); + + println!("Connecting to Unix socket..."); + let (tx0, rx0) = oneshot::channel::(); + + /* NOTE: More phases will probably exist in the future */ + enum AppPhase { + Normal, + Shutdown, + } + + let (s_tx, mut s_rx) = watch::channel::(AppPhase::Normal); + + let rt = Runtime::new().unwrap(); + let _guard = rt.enter(); + /* spawn the thread that manages the unix socket */ + tokio::spawn(async move { + let p = Path::new("./boarders.sock"); + if p.exists() { + std::fs::remove_file(p).unwrap(); + } + let socket = UnixListener::bind("./boarders.sock").unwrap_or_else(|_| { + panic!("{}", style("Could not connect to Unix socket").red()); + }); + println!("{}", style("Listening on Unix socket!").green()); + if tx0.send(true).is_err() { + panic!( + "{}", + style("[FATAL] Thread reciever was dropped! (should never happen)") + .red() + .bold() + ); + } + + /* + * HACK: clone the reciever a bunch of times to get around moves + * not sure if there's a better way + */ + let s_rx_0 = s_rx.clone(); + let spawn_t = tokio::spawn(async move { + let s_rx = s_rx_0.clone(); + loop { + let mut s_rx = s_rx.clone(); + /* accept socket then wait for exit, or shutdown signal */ + let (conn, _addr) = socket.accept().await.unwrap(); + tokio::spawn(async move { + select! { + _socket = socket_loop(&conn) => 0, + _state = s_rx.changed() => 1 + } + }); + } + }); + + /* + * FIXME: use enums when I feel like it (not urgent) + */ + match select! { + _st = spawn_t => 0, + state = s_rx.changed() => if state.is_ok() { + match *s_rx.borrow() { + AppPhase::Shutdown => 1, + _ => 0 + } + } else { + 0 + } + } { + 0 => println!("Client disconnected"), + 1 => println!("Shut down client thread(s)"), + _ => println!("Unexpected value from client thread"), + } + }); + + /* why do I want a special message for that? Honestly I don't know... */ + rx0.await.unwrap_or_else(|_| { + panic!( + "{}", + style("[FATAL] Thread transmitter was dropped! (should never happen)") + .red() + .bold() + ) + }); + + println!("{}", style("Finished initialization").green().bold()); + println!("Starting server..."); + + let server = + HttpServer::new(move || { + App::new() + .data(ThreadData { + handlebars: { + let mut h = Handlebars::new(); + /* + * TODO: allow user to specify wether or not templates should be + * loaded dynamically or statically, probably via environment + * variable at compile time + */ + /* register handlebars partials here */ + handlebars_helper!(msg_helper: |s: str| format!("helped: {}", s.to_string())); + h.register_helper("msg", Box::new(msg_helper)); + h.register_partial("head", include_str!("res/snippets/head.hbs")).unwrap(); + h.register_partial("login_status", include_str!("res/snippets/login_status.hbs")).unwrap(); + h.register_partial("board_bar", include_str!("res/snippets/board_bar.hbs")).unwrap(); + /* register handlebars templates here */ + h.register_template_string("board_index", include_str!("res/board_index.hbs")).unwrap(); + h.register_template_string("welcome", include_str!("res/welcome.hbs")).unwrap(); + h.register_template_string("auth", include_str!("res/auth.hbs")).unwrap(); + h.register_template_string("board", include_str!("res/board.hbs")).unwrap(); + h + } + }).app_data(web_data.clone()) + .service(index) + .service(debug) + .service(list_boards) + .service(login) + .service(auth_html) + .service(sign_up) + .service(sign_up_result) + .service(new_board) + .service(new_board_result) + .service(get_board) + .service(board_post) + .service( + /* add all api services under the /api/ scope */ + web::scope("/api") + .service(api::boards::new) + .service(api::boards::list) + .service(api::messages::new) + .service(api::users::new) + .service(api::users::list) + .service(api::users::get) + .service(api::users::auth)) + /* serve all static files inside ../static/ */ + .service(actix_files::Files::new("/static/", "./static/").show_files_listing()) + }) + .bind("127.0.0.1:8080")? + .run(); + println!("Started server"); + println!( + "{}v{}", + include_str!("res/banner"), + env!("CARGO_PKG_VERSION") + ); + + /* + * Program will block here until it is Ctrl-C 'd on the command line + * TODO: Block until cmd line exit OR client shutdown command + */ + let res = server.await; + + println!("\nExiting..."); + /* + * Note to self: handle all errors as much as possible + * Use unwrap() as little as possible + * If anything goes wrong while saving data data WILL get lost + */ + println!("Saving application state..."); + for b in wb.boards.lock().unwrap().iter() { + fs::create_dir_all(&b.path) + .unwrap_or_else(|x| println!("Could not create directory {:?}: {}", b.path, x)); + b.save_state("board.json") + .unwrap_or_else(|x| println!("Could not save board {:?}: {}", b.title, x)); + } + + wb.board_factory + .lock() + .unwrap() + .save_state("factory.json") + .unwrap_or_else(|x| println!("Could not save user factory state: {}", x)); + + wb.msg_factory + .lock() + .unwrap() + .save_state("message_factory.json") + .unwrap_or_else(|x| println!("Could not save message factory state: {}", x)); + + wb.user_manager + .lock() + .unwrap() + .save_state("users.json") + .unwrap_or_else(|x| println!("Could not save user data: {}", x)); + + /* send shutdown signal to all unix socket threads */ + println!("Shutting down client threads..."); + if s_tx.send(AppPhase::Shutdown).is_err() { + println!( + "{}", + style("Could not update the app phase to Shutdown") + .red() + .bold() + ); + } + rt.shutdown_timeout(std::time::Duration::from_secs(10)); + println!("Done!"); + res +} diff --git a/src/res/auth.hbs b/v1/src/res/auth.hbs similarity index 100% rename from src/res/auth.hbs rename to v1/src/res/auth.hbs diff --git a/src/res/banner b/v1/src/res/banner similarity index 100% rename from src/res/banner rename to v1/src/res/banner diff --git a/src/res/board.hbs b/v1/src/res/board.hbs similarity index 100% rename from src/res/board.hbs rename to v1/src/res/board.hbs diff --git a/src/res/board_index.hbs b/v1/src/res/board_index.hbs similarity index 100% rename from src/res/board_index.hbs rename to v1/src/res/board_index.hbs diff --git a/src/res/new_account_redirect.hbs b/v1/src/res/new_account_redirect.hbs similarity index 100% rename from src/res/new_account_redirect.hbs rename to v1/src/res/new_account_redirect.hbs diff --git a/src/res/snippets/board_bar.hbs b/v1/src/res/snippets/board_bar.hbs similarity index 100% rename from src/res/snippets/board_bar.hbs rename to v1/src/res/snippets/board_bar.hbs diff --git a/src/res/snippets/head.hbs b/v1/src/res/snippets/head.hbs similarity index 100% rename from src/res/snippets/head.hbs rename to v1/src/res/snippets/head.hbs diff --git a/src/res/snippets/login_status.hbs b/v1/src/res/snippets/login_status.hbs similarity index 100% rename from src/res/snippets/login_status.hbs rename to v1/src/res/snippets/login_status.hbs diff --git a/src/res/welcome.hbs b/v1/src/res/welcome.hbs similarity index 100% rename from src/res/welcome.hbs rename to v1/src/res/welcome.hbs diff --git a/static/account_login.html b/v1/static/account_login.html similarity index 100% rename from static/account_login.html rename to v1/static/account_login.html diff --git a/static/index.css b/v1/static/index.css similarity index 100% rename from static/index.css rename to v1/static/index.css diff --git a/static/navbar.html b/v1/static/navbar.html similarity index 100% rename from static/navbar.html rename to v1/static/navbar.html diff --git a/static/new_account.html b/v1/static/new_account.html similarity index 100% rename from static/new_account.html rename to v1/static/new_account.html diff --git a/static/new_board.html b/v1/static/new_board.html similarity index 100% rename from static/new_board.html rename to v1/static/new_board.html diff --git a/v2/.gitignore b/v2/.gitignore new file mode 100644 index 0000000..13d5119 --- /dev/null +++ b/v2/.gitignore @@ -0,0 +1 @@ +boarders.v2 diff --git a/v2/go.mod b/v2/go.mod new file mode 100644 index 0000000..306b0d6 --- /dev/null +++ b/v2/go.mod @@ -0,0 +1,3 @@ +module boarders.v2 + +go 1.22.0 diff --git a/v2/main.go b/v2/main.go new file mode 100644 index 0000000..df0cf7d --- /dev/null +++ b/v2/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "fmt" +) + +func main() { + fmt.Println("Hello Boarders!") +}