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