Logging and Notifications
This commit is contained in:
@@ -11,8 +11,10 @@ config = "0.14.0"
|
||||
ctrlc = "3.4.5"
|
||||
derive_more = { version = "1.0", features = ["full"] }
|
||||
directories = "5.0"
|
||||
dotenvy = "0.15.7"
|
||||
image = "0.25"
|
||||
interprocess = { version = "2.2.1", features = ["tokio"] }
|
||||
tauri-winrt-notification = "0.6.0"
|
||||
open = "5.3.0"
|
||||
rand = "0.8.5"
|
||||
reqwest = { version = "0.12.8", default-features = false, features = [
|
||||
@@ -23,8 +25,11 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_qs = "0.13.0"
|
||||
sha256 = "1.5.0"
|
||||
thiserror = { version = "1.0" }
|
||||
time = "0.3.36"
|
||||
tokio = { version = "1.40.0", features = ["full"] }
|
||||
toml = "0.8"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["time"] }
|
||||
tray-icon = "0.19"
|
||||
uuid = { version = "1.10.0", features = ["fast-rng", "serde", "v4"] }
|
||||
winit = "0.30"
|
||||
|
@@ -5,11 +5,25 @@ use tray_icon::menu::{MenuId, MenuItem};
|
||||
use winit::{
|
||||
application::ApplicationHandler,
|
||||
event::StartCause,
|
||||
event_loop::{ActiveEventLoop, ControlFlow},
|
||||
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
|
||||
};
|
||||
|
||||
use crate::{config::Config, icon::TrayIcon, oauth, state_machine::Event, BASE_URL};
|
||||
|
||||
pub async fn start(
|
||||
config: Config,
|
||||
sender: Sender<Event>,
|
||||
receiver: Receiver<Event>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let mut app = App::new(config, sender, receiver)?;
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
event_loop.run_app(&mut app)?;
|
||||
tracing::info!("EventLoop Shutdown");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
config: Config,
|
||||
tray_icon: TrayIcon,
|
||||
@@ -112,7 +126,7 @@ impl ApplicationHandler for App {
|
||||
.unwrap();
|
||||
}
|
||||
Event::Quit => {
|
||||
println!("Shutting down EventLoop");
|
||||
tracing::info!("Shutting down EventLoop");
|
||||
event_loop.exit()
|
||||
}
|
||||
_ => {}
|
||||
@@ -126,6 +140,16 @@ impl ApplicationHandler for App {
|
||||
let _ = self.sender.send(Event::Ready {
|
||||
config: self.config.clone(),
|
||||
});
|
||||
|
||||
if !self.config.toast_shown() {
|
||||
tauri_winrt_notification::Toast::new(crate::AVAM_APP_ID)
|
||||
.title("Avam lives in the system tray!")
|
||||
.text1("(╯°□°)╯︵ ┻━┻")
|
||||
.show()
|
||||
.expect("unable to toast");
|
||||
}
|
||||
|
||||
let _ = self.config.set_toast_shown(true);
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
|
@@ -15,6 +15,8 @@ use crate::{
|
||||
pub struct Config {
|
||||
#[serde(with = "arc_rwlock_serde")]
|
||||
token: Arc<RwLock<Option<String>>>,
|
||||
#[serde(with = "arc_rwlock_serde")]
|
||||
toast_shown: Arc<RwLock<bool>>,
|
||||
#[serde(skip)]
|
||||
code_verifier: Arc<RwLock<Option<CodeVerifier>>>,
|
||||
#[serde(skip)]
|
||||
@@ -27,6 +29,7 @@ impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
token: Default::default(),
|
||||
toast_shown: Default::default(),
|
||||
code_verifier: Default::default(),
|
||||
code_challenge_method: Default::default(),
|
||||
open_browser: Arc::new(RwLock::new(true)),
|
||||
@@ -49,6 +52,8 @@ pub enum ConfigError {
|
||||
#[error(transparent)]
|
||||
Toml(#[from] toml::ser::Error),
|
||||
#[error(transparent)]
|
||||
Config(#[from] config::ConfigError),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
@@ -64,6 +69,18 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(crate) fn toast_shown(&self) -> bool {
|
||||
*self.toast_shown.read().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn set_toast_shown(&self, value: bool) -> Result<(), ConfigError> {
|
||||
*self.toast_shown.write().unwrap() = value;
|
||||
self.write()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn set_code_verifier(
|
||||
&self,
|
||||
@@ -110,7 +127,12 @@ impl Config {
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let config: Self = config.try_deserialize().unwrap_or_default();
|
||||
let token = config.get_string("token").ok();
|
||||
let toast_shown = config.get_bool("toast_shown").unwrap_or_default();
|
||||
let config = Self::default();
|
||||
|
||||
config.set_token(token)?;
|
||||
config.set_toast_shown(toast_shown)?;
|
||||
|
||||
config.write()?;
|
||||
|
||||
|
@@ -40,4 +40,23 @@ impl Dirs {
|
||||
c.push(".lock");
|
||||
Ok(c)
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn get_log_dir() -> Result<PathBuf, DirsError> {
|
||||
let c = Self::get_config_dir()?;
|
||||
let mut log_dir = c.parent().unwrap().to_path_buf();
|
||||
log_dir.push("logs");
|
||||
if !log_dir.exists() {
|
||||
std::fs::create_dir_all(&log_dir)?;
|
||||
}
|
||||
|
||||
Ok(log_dir.to_path_buf())
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn get_log_file() -> Result<PathBuf, DirsError> {
|
||||
let mut l = Self::get_log_dir()?;
|
||||
l.push("avam.log");
|
||||
Ok(l)
|
||||
}
|
||||
}
|
||||
|
@@ -97,7 +97,7 @@ impl TrayIcon {
|
||||
if let Some(item) = self.menu_items.get(id) {
|
||||
if let Some(i) = self.menu.items().iter().find(|i| i.id() == id) {
|
||||
if let Err(e) = item(i) {
|
||||
eprintln!("{:#?}", e);
|
||||
tracing::error!("{:#?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
#![allow(clippy::needless_return)]
|
||||
|
||||
mod app;
|
||||
@@ -17,7 +18,9 @@ use oauth::{start_code_listener, start_code_to_token};
|
||||
use pipe::Pipe;
|
||||
use state_machine::Event;
|
||||
use tokio::task::JoinSet;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
pub static AVAM_APP_ID: &str = "AvamToast-ECEB71694A5E6105";
|
||||
pub static BASE_URL: &str = "https://avam.avii.nl";
|
||||
pub static PROJECT_NAME: &str = "Avii's Virtual Airline Manager";
|
||||
pub static COPYRIGHT: &str = "Avii's Virtual Airline Manager © 2024";
|
||||
@@ -35,6 +38,10 @@ pub struct Arguments {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
init_logging()?;
|
||||
|
||||
let (event_sender, event_receiver) = tokio::sync::broadcast::channel(1);
|
||||
let args = Arguments::parse();
|
||||
|
||||
@@ -43,6 +50,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||
}
|
||||
|
||||
register_url_scheme()?;
|
||||
register_notification_handler()?;
|
||||
|
||||
let config = Config::new()?;
|
||||
|
||||
@@ -52,7 +60,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||
let sender = event_sender.clone();
|
||||
let mut ctrl_c_counter = 0;
|
||||
ctrlc::set_handler(move || {
|
||||
println!("CTRL_C: Quit singal sent");
|
||||
tracing::info!("CTRL_C: Quit singal sent");
|
||||
ctrl_c_counter += 1;
|
||||
if ctrl_c_counter >= 3 {
|
||||
let _ = unregister_url_scheme();
|
||||
@@ -61,7 +69,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||
}
|
||||
|
||||
if let Err(e) = sender.send(Event::Quit) {
|
||||
println!("{:#?}", e)
|
||||
tracing::error!("{:#?}", e)
|
||||
};
|
||||
})?;
|
||||
|
||||
@@ -86,7 +94,7 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||
let c = config.clone();
|
||||
let sender = event_sender.clone();
|
||||
let receiver = event_receiver.resubscribe();
|
||||
start_tray_icon(c, sender, receiver).await?;
|
||||
app::start(c, sender, receiver).await?;
|
||||
|
||||
// Wait for everything to finish
|
||||
while let Some(result) = futures.join_next().await {
|
||||
@@ -97,28 +105,12 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||
|
||||
// Cleanup
|
||||
unregister_url_scheme()?;
|
||||
unregister_notification_handler()?;
|
||||
Lock::unlock();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
use app::App;
|
||||
use tokio::sync::broadcast::{Receiver, Sender};
|
||||
use winit::event_loop::EventLoop;
|
||||
async fn start_tray_icon(
|
||||
config: Config,
|
||||
sender: Sender<Event>,
|
||||
receiver: Receiver<Event>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let mut app = App::new(config, sender, receiver)?;
|
||||
let event_loop = EventLoop::new()?;
|
||||
|
||||
event_loop.run_app(&mut app)?;
|
||||
println!("EventLoop Shutdonw");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -178,3 +170,72 @@ fn unregister_url_scheme() -> Result<(), anyhow::Error> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_notification_handler() -> Result<(), anyhow::Error> {
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
let avam_schema_root = hkcu.create_subkey(format!(
|
||||
"Software\\Classes\\AppUserModelId\\{}",
|
||||
AVAM_APP_ID
|
||||
))?;
|
||||
let current_exec = std::env::current_exe()?;
|
||||
let mut icon = current_exec.parent().unwrap().to_path_buf();
|
||||
icon.push("icon.png");
|
||||
|
||||
avam_schema_root
|
||||
.0
|
||||
.set_value("DisplayName", &crate::PROJECT_NAME)?;
|
||||
avam_schema_root.0.set_value("IconBackgroundColor", &"0")?;
|
||||
avam_schema_root
|
||||
.0
|
||||
.set_value("IconUri", &icon.to_str().unwrap())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unregister_notification_handler() -> Result<(), anyhow::Error> {
|
||||
use winreg::enums::*;
|
||||
use winreg::RegKey;
|
||||
|
||||
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
||||
hkcu.delete_subkey_all(format!(
|
||||
"Software\\Classes\\AppUserModelId\\{}",
|
||||
AVAM_APP_ID
|
||||
))
|
||||
.ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_logging() -> Result<(), anyhow::Error> {
|
||||
#[cfg(not(debug_assertions))]
|
||||
use dirs::Dirs;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let log_file = Dirs::get_log_file()?;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
if !log_file.exists() {
|
||||
fs::write(&log_file, "")?;
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let file = File::options().append(true).open(&log_file)?;
|
||||
|
||||
let fmt = tracing_subscriber::fmt::layer();
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let fmt = fmt.with_ansi(false).with_writer(Arc::new(file));
|
||||
|
||||
tracing_subscriber::registry().with(fmt).init();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -60,7 +60,7 @@ pub async fn start_code_to_token(
|
||||
tokio::select! {
|
||||
_ = sleep(Duration::from_millis(100)) => {
|
||||
if let Ok(Event::Quit) = event_receiver.try_recv() {
|
||||
println!("Shutting down Code Transformer");
|
||||
tracing::info!("Shutting down Code Transformer");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,6 @@ pub async fn start_code_to_token(
|
||||
}
|
||||
}
|
||||
|
||||
println!("Code Transformer Shutdown");
|
||||
tracing::info!("Code Transformer Shutdown");
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -61,20 +61,20 @@ impl Pipe {
|
||||
let new_sender = pipe_sender.clone();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = Self::handle_conn(conn, new_sender).await {
|
||||
eprintln!("error while handling connection: {e}");
|
||||
tracing::error!("error while handling connection: {e}");
|
||||
}
|
||||
});
|
||||
},
|
||||
_ = sleep(Duration::from_millis(100)) => {
|
||||
if let Ok(Event::Quit) = quit_signal.try_recv() {
|
||||
println!("Shutting down Code Listener");
|
||||
tracing::info!("Shutting down Code Listener");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Code Listener Shutdown");
|
||||
tracing::info!("Code Listener Shutdown");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@@ -137,7 +137,7 @@ pub async fn start(
|
||||
loop {
|
||||
if let Ok(event) = event_receiver.recv().await {
|
||||
if event == Event::Quit {
|
||||
println!("Shutting down State Machine");
|
||||
tracing::info!("Shutting down State Machine");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -153,6 +153,6 @@ pub async fn start(
|
||||
}
|
||||
}
|
||||
|
||||
println!("State Machine Shutdown");
|
||||
tracing::info!("State Machine Shutdown");
|
||||
Ok(())
|
||||
}
|
||||
|
Reference in New Issue
Block a user