Skip to content

Commit 98707d2

Browse files
authored
Add Service Framework (#21)
* Implement service framework - Make main async - Implement Status enum - Implement Priority enum - Implement ServiceInfo struct - Implement ServiceInternals trait - Implement Service trait * Bot library - Add fern crate - Add humantime crate - Add log crate - Implement Bot - Implement BotBuilder - Refactor config Display trait implementation - Implement library is_debug() function - Implement library run(Bot) function - Implement log module (log::setup(), log::is_set_up() and log::get_min_log_level()) - Adapt main to new changes * WIP: Finish services framework Just a lot of refactoring and fixing. No time to describe all this now. Happy new year! :) * Finish services framework Too much to describe. It's done, that's it. This was one hell of a ride.
1 parent e1f2040 commit 98707d2

File tree

7 files changed

+685
-11
lines changed

7 files changed

+685
-11
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ repository = "https://github.com/Kitt3120/lum"
1313

1414
[dependencies]
1515
dirs = "5.0.1"
16+
fern = { version = "0.6.2", features = ["chrono", "colored", "date-based"] }
17+
humantime = "2.1.0"
18+
log = { version = "0.4.20", features = ["serde"] }
1619
serde = { version = "1.0.193", features = ["derive"] }
1720
serde_json = "1.0.108"
1821
sqlx = { version = "0.7.3", features = ["runtime-tokio", "any", "postgres", "mysql", "sqlite", "tls-native-tls", "migrate", "macros", "uuid", "chrono", "json"] }

src/bot.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use crate::service::{PinnedBoxedFuture, Service, ServiceManager, ServiceManagerBuilder};
2+
3+
pub struct BotBuilder {
4+
name: String,
5+
service_manager: ServiceManagerBuilder,
6+
}
7+
8+
impl BotBuilder {
9+
pub fn new(name: &str) -> Self {
10+
Self {
11+
name: name.to_string(),
12+
service_manager: ServiceManager::builder(),
13+
}
14+
}
15+
16+
pub fn with_service(mut self, service: Box<dyn Service>) -> Self {
17+
self.service_manager = self.service_manager.with_service(service); // The ServiceManagerBuilder itself will warn when adding a service multiple times
18+
19+
self
20+
}
21+
22+
pub fn with_services(mut self, services: Vec<Box<dyn Service>>) -> Self {
23+
for service in services {
24+
self.service_manager = self.service_manager.with_service(service);
25+
}
26+
27+
self
28+
}
29+
30+
pub fn build(self) -> Bot {
31+
Bot::from(self)
32+
}
33+
}
34+
35+
pub struct Bot {
36+
pub name: String,
37+
pub service_manager: ServiceManager,
38+
}
39+
40+
impl Bot {
41+
pub fn builder(name: &str) -> BotBuilder {
42+
BotBuilder::new(name)
43+
}
44+
45+
//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
46+
pub fn start(&mut self) -> PinnedBoxedFuture<'_, ()> {
47+
Box::pin(async move {
48+
self.service_manager.start_services().await;
49+
//TODO: Potential for further initialization here, like modules
50+
})
51+
}
52+
53+
//TODO: When Rust allows async trait methods to be object-safe, refactor this to use async instead of returning a future
54+
pub fn stop(&mut self) -> PinnedBoxedFuture<'_, ()> {
55+
Box::pin(async move {
56+
self.service_manager.stop_services().await;
57+
//TODO: Potential for further deinitialization here, like modules
58+
})
59+
}
60+
}
61+
62+
impl From<BotBuilder> for Bot {
63+
fn from(builder: BotBuilder) -> Self {
64+
Self {
65+
name: builder.name,
66+
service_manager: builder.service_manager.build(),
67+
}
68+
}
69+
}

src/config.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use core::fmt;
2+
use serde::{Deserialize, Serialize};
23
use std::{
34
fmt::{Display, Formatter},
45
fs, io,
56
path::PathBuf,
67
};
7-
8-
use serde::{Deserialize, Serialize};
98
use thiserror::Error;
109

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

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

5554
impl Display for Config {
5655
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
57-
write!(f, "discord_token: {}", self.discord_token)
56+
let content = match serde_json::to_string(self) {
57+
Ok(content) => content,
58+
Err(error) => {
59+
return write!(f, "Unable to serialize config: {}", error);
60+
}
61+
};
62+
63+
write!(f, "{}", content)
5864
}
5965
}
6066

src/lib.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use crate::service::OverallStatus;
2+
use ::log::{error, info};
3+
use bot::Bot;
4+
use std::time::SystemTime;
5+
6+
pub mod bot;
7+
pub mod config;
8+
pub mod log;
9+
pub mod service;
10+
11+
pub fn is_debug() -> bool {
12+
cfg!(debug_assertions)
13+
}
14+
15+
pub async fn run(mut bot: Bot) {
16+
if !log::is_set_up() {
17+
eprintln!("Logger has not been set up!\n{} will exit.", bot.name);
18+
19+
return;
20+
}
21+
22+
let now = SystemTime::now();
23+
24+
bot.start().await;
25+
26+
match now.elapsed() {
27+
Ok(elapsed) => info!("Startup took {}ms", elapsed.as_millis()),
28+
Err(error) => {
29+
error!(
30+
"Error getting elapsed startup time: {}\n{} will exit.",
31+
error, bot.name
32+
);
33+
34+
return;
35+
}
36+
};
37+
38+
if bot.service_manager.overall_status().await != OverallStatus::Healthy {
39+
let status_tree = bot.service_manager.status_tree().await;
40+
41+
error!("{} is not healthy! Some essential services did not start up successfully. Please check the logs.\nService status tree:\n{}\n{} will exit.",
42+
bot.name,
43+
status_tree,
44+
bot.name);
45+
return;
46+
}
47+
48+
info!("{} is alive", bot.name,);
49+
50+
//TODO: Add CLI commands
51+
match tokio::signal::ctrl_c().await {
52+
Ok(_) => {
53+
info!("Received SIGINT, {} will now shut down", bot.name);
54+
}
55+
Err(error) => {
56+
panic!("Error receiving SIGINT: {}\n{} will exit.", error, bot.name);
57+
}
58+
}
59+
60+
bot.stop().await;
61+
62+
info!("{} has shut down", bot.name);
63+
}

src/log.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use fern::colors::{Color, ColoredLevelConfig};
2+
use log::{LevelFilter, SetLoggerError};
3+
use std::{
4+
io,
5+
sync::atomic::{AtomicBool, Ordering},
6+
time::SystemTime,
7+
};
8+
9+
use crate::is_debug;
10+
11+
static IS_LOGGER_SET_UP: AtomicBool = AtomicBool::new(false);
12+
13+
pub fn is_set_up() -> bool {
14+
IS_LOGGER_SET_UP.load(Ordering::Relaxed)
15+
}
16+
17+
pub fn setup() -> Result<(), SetLoggerError> {
18+
let colors = ColoredLevelConfig::new()
19+
.info(Color::Green)
20+
.debug(Color::Magenta)
21+
.warn(Color::Yellow)
22+
.error(Color::Red)
23+
.trace(Color::Cyan);
24+
25+
fern::Dispatch::new()
26+
.format(move |out, message, record| {
27+
out.finish(format_args!(
28+
"[{} {: <25} {: <5}] {}",
29+
humantime::format_rfc3339_seconds(SystemTime::now()),
30+
record.target(),
31+
colors.color(record.level()),
32+
message
33+
))
34+
})
35+
.level(get_min_log_level())
36+
.chain(io::stdout())
37+
.apply()?;
38+
39+
IS_LOGGER_SET_UP.store(true, Ordering::Relaxed);
40+
41+
Ok(())
42+
}
43+
44+
fn get_min_log_level() -> LevelFilter {
45+
if is_debug() {
46+
LevelFilter::Debug
47+
} else {
48+
LevelFilter::Info
49+
}
50+
}

src/main.rs

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,57 @@
1-
mod config;
1+
use ::log::{error, warn};
2+
use lum::{
3+
bot::Bot,
4+
config::{Config, ConfigHandler, ConfigParseError},
5+
log,
6+
service::Service,
7+
};
28

3-
pub const BOT_NAME: &str = "Lum";
9+
const BOT_NAME: &str = "Lum";
410

5-
fn main() {
6-
let config_handler = config::ConfigHandler::new(BOT_NAME.to_lowercase().as_str());
7-
let config = match config_handler.get_config() {
11+
#[tokio::main]
12+
async fn main() {
13+
setup_logger();
14+
15+
if lum::is_debug() {
16+
warn!("THIS IS A DEBUG RELEASE!");
17+
}
18+
19+
let _config = match get_config() {
820
Ok(config) => config,
921
Err(err) => {
10-
panic!("Error reading config file: {}", err);
22+
error!(
23+
"Error reading config file: {}\n{} will exit.",
24+
err, BOT_NAME
25+
);
26+
27+
return;
1128
}
1229
};
1330

14-
println!("Config: {}", config);
31+
let bot = Bot::builder(BOT_NAME)
32+
.with_services(initialize_services())
33+
.build();
34+
35+
lum::run(bot).await;
36+
}
37+
38+
fn setup_logger() {
39+
if let Err(error) = log::setup() {
40+
panic!(
41+
"Error setting up the Logger: {}\n{} will exit.",
42+
error, BOT_NAME
43+
);
44+
}
45+
}
46+
47+
fn get_config() -> Result<Config, ConfigParseError> {
48+
let config_handler = ConfigHandler::new(BOT_NAME.to_lowercase().as_str());
49+
config_handler.get_config()
50+
}
51+
52+
fn initialize_services() -> Vec<Box<dyn Service>> {
53+
//TODO: Add services
54+
//...
55+
56+
vec![]
1557
}

0 commit comments

Comments
 (0)