Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ repository = "https://github.com/Kitt3120/lum"

[dependencies]
dirs = "5.0.1"
fern = { version = "0.6.2", features = ["chrono", "colored", "date-based"] }
humantime = "2.1.0"
log = { version = "0.4.20", features = ["serde"] }
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
sqlx = { version = "0.7.3", features = ["runtime-tokio", "any", "postgres", "mysql", "sqlite", "tls-native-tls", "migrate", "macros", "uuid", "chrono", "json"] }
Expand Down
69 changes: 69 additions & 0 deletions src/bot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use crate::service::{PinnedBoxedFuture, Service, ServiceManager, ServiceManagerBuilder};

pub struct BotBuilder {
name: String,
service_manager: ServiceManagerBuilder,
}

impl BotBuilder {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
service_manager: ServiceManager::builder(),
}
}

pub fn with_service(mut self, service: Box<dyn Service>) -> Self {
self.service_manager = self.service_manager.with_service(service); // The ServiceManagerBuilder itself will warn when adding a service multiple times

self
}

pub fn with_services(mut self, services: Vec<Box<dyn Service>>) -> Self {
for service in services {
self.service_manager = self.service_manager.with_service(service);
}

self
}

pub fn build(self) -> Bot {
Bot::from(self)
}
}

pub struct Bot {
pub name: String,
pub service_manager: ServiceManager,
}

impl Bot {
pub fn builder(name: &str) -> BotBuilder {
BotBuilder::new(name)
}

//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
pub fn start(&mut self) -> PinnedBoxedFuture<'_, ()> {
Box::pin(async move {
self.service_manager.start_services().await;
//TODO: Potential for further initialization here, like modules
})
}

//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
pub fn stop(&mut self) -> PinnedBoxedFuture<'_, ()> {
Box::pin(async move {
self.service_manager.stop_services().await;
//TODO: Potential for further deinitialization here, like modules
})
}
}

impl From<BotBuilder> for Bot {
fn from(builder: BotBuilder) -> Self {
Self {
name: builder.name,
service_manager: builder.service_manager.build(),
}
}
}
14 changes: 10 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use core::fmt;
use serde::{Deserialize, Serialize};
use std::{
fmt::{Display, Formatter},
fs, io,
path::PathBuf,
};

use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Debug, Error)]
Expand Down Expand Up @@ -38,7 +37,7 @@ fn discord_token_default() -> String {
String::from("Please provide a token")
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[derive(Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct Config {
#[serde(rename = "discordToken", default = "discord_token_default")]
pub discord_token: String,
Expand All @@ -54,7 +53,14 @@ impl Default for Config {

impl Display for Config {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "discord_token: {}", self.discord_token)
let content = match serde_json::to_string(self) {
Ok(content) => content,
Err(error) => {
return write!(f, "Unable to serialize config: {}", error);
}
};

write!(f, "{}", content)
}
}

Expand Down
63 changes: 63 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::service::OverallStatus;
use ::log::{error, info};
use bot::Bot;
use std::time::SystemTime;

pub mod bot;
pub mod config;
pub mod log;
pub mod service;

pub fn is_debug() -> bool {
cfg!(debug_assertions)
}

pub async fn run(mut bot: Bot) {
if !log::is_set_up() {
eprintln!("Logger has not been set up!\n{} will exit.", bot.name);

return;
}

let now = SystemTime::now();

bot.start().await;

match now.elapsed() {
Ok(elapsed) => info!("Startup took {}ms", elapsed.as_millis()),
Err(error) => {
error!(
"Error getting elapsed startup time: {}\n{} will exit.",
error, bot.name
);

return;
}
};

if bot.service_manager.overall_status().await != OverallStatus::Healthy {
let status_tree = bot.service_manager.status_tree().await;

error!("{} is not healthy! Some essential services did not start up successfully. Please check the logs.\nService status tree:\n{}\n{} will exit.",
bot.name,
status_tree,
bot.name);
return;
}

info!("{} is alive", bot.name,);

//TODO: Add CLI commands
match tokio::signal::ctrl_c().await {
Ok(_) => {
info!("Received SIGINT, {} will now shut down", bot.name);
}
Err(error) => {
panic!("Error receiving SIGINT: {}\n{} will exit.", error, bot.name);
}
}

bot.stop().await;

info!("{} has shut down", bot.name);
}
50 changes: 50 additions & 0 deletions src/log.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use fern::colors::{Color, ColoredLevelConfig};
use log::{LevelFilter, SetLoggerError};
use std::{
io,
sync::atomic::{AtomicBool, Ordering},
time::SystemTime,
};

use crate::is_debug;

static IS_LOGGER_SET_UP: AtomicBool = AtomicBool::new(false);

pub fn is_set_up() -> bool {
IS_LOGGER_SET_UP.load(Ordering::Relaxed)
}

pub fn setup() -> Result<(), SetLoggerError> {
let colors = ColoredLevelConfig::new()
.info(Color::Green)
.debug(Color::Magenta)
.warn(Color::Yellow)
.error(Color::Red)
.trace(Color::Cyan);

fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"[{} {: <25} {: <5}] {}",
humantime::format_rfc3339_seconds(SystemTime::now()),
record.target(),
colors.color(record.level()),
message
))
})
.level(get_min_log_level())
.chain(io::stdout())
.apply()?;

IS_LOGGER_SET_UP.store(true, Ordering::Relaxed);

Ok(())
}

fn get_min_log_level() -> LevelFilter {
if is_debug() {
LevelFilter::Debug
} else {
LevelFilter::Info
}
}
56 changes: 49 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,57 @@
mod config;
use ::log::{error, warn};
use lum::{
bot::Bot,
config::{Config, ConfigHandler, ConfigParseError},
log,
service::Service,
};

pub const BOT_NAME: &str = "Lum";
const BOT_NAME: &str = "Lum";

fn main() {
let config_handler = config::ConfigHandler::new(BOT_NAME.to_lowercase().as_str());
let config = match config_handler.get_config() {
#[tokio::main]
async fn main() {
setup_logger();

if lum::is_debug() {
warn!("THIS IS A DEBUG RELEASE!");
}

let _config = match get_config() {
Ok(config) => config,
Err(err) => {
panic!("Error reading config file: {}", err);
error!(
"Error reading config file: {}\n{} will exit.",
err, BOT_NAME
);

return;
}
};

println!("Config: {}", config);
let bot = Bot::builder(BOT_NAME)
.with_services(initialize_services())
.build();

lum::run(bot).await;
}

fn setup_logger() {
if let Err(error) = log::setup() {
panic!(
"Error setting up the Logger: {}\n{} will exit.",
error, BOT_NAME
);
}
}

fn get_config() -> Result<Config, ConfigParseError> {
let config_handler = ConfigHandler::new(BOT_NAME.to_lowercase().as_str());
config_handler.get_config()
}

fn initialize_services() -> Vec<Box<dyn Service>> {
//TODO: Add services
//...

vec![]
}
Loading