Created Boardersv2 moved old to Boardersv1
This commit is contained in:
parent
fa924285b0
commit
2571ce5756
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,8 +0,0 @@
|
|||||||
|
|
||||||
/target
|
|
||||||
|
|
||||||
# new data directory, specified in config gile
|
|
||||||
# NOTE: this config file does not yet exist
|
|
||||||
/data
|
|
||||||
|
|
||||||
Cargo.lock
|
|
447
src/main.rs
447
src/main.rs
@ -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<ThreadData<'_>>
|
|
||||||
) -> 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<AppState>
|
|
||||||
) -> impl Responder {
|
|
||||||
HttpResponse::Ok().body(serde_json::to_string_pretty(data.as_ref()).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn socket_loop(socket: &UnixStream) -> Result<(), Box<dyn Error>> {
|
|
||||||
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<String> = 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<Board> = 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::<bool>();
|
|
||||||
|
|
||||||
/* NOTE: More phases will probably exist in the future */
|
|
||||||
enum AppPhase {
|
|
||||||
Normal,
|
|
||||||
Shutdown
|
|
||||||
}
|
|
||||||
|
|
||||||
let (s_tx, mut s_rx) = watch::channel::<AppPhase>(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
|
|
||||||
}
|
|
6
v1/.gitignore
vendored
Normal file
6
v1/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
target
|
||||||
|
|
||||||
|
data
|
||||||
|
|
||||||
|
Cargo.lock
|
480
v1/src/main.rs
Normal file
480
v1/src/main.rs
Normal file
@ -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<ThreadData<'_>>) -> 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<AppState>) -> impl Responder {
|
||||||
|
HttpResponse::Ok().body(serde_json::to_string_pretty(data.as_ref()).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn socket_loop(socket: &UnixStream) -> Result<(), Box<dyn Error>> {
|
||||||
|
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<String> = 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<Board> = 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::<bool>();
|
||||||
|
|
||||||
|
/* NOTE: More phases will probably exist in the future */
|
||||||
|
enum AppPhase {
|
||||||
|
Normal,
|
||||||
|
Shutdown,
|
||||||
|
}
|
||||||
|
|
||||||
|
let (s_tx, mut s_rx) = watch::channel::<AppPhase>(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
|
||||||
|
}
|
1
v2/.gitignore
vendored
Normal file
1
v2/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
boarders.v2
|
9
v2/main.go
Normal file
9
v2/main.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Hello Boarders!")
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user