267 lines
6.8 KiB
Rust
267 lines
6.8 KiB
Rust
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
|
#![allow(clippy::needless_return)]
|
|
|
|
mod app;
|
|
mod client;
|
|
mod config;
|
|
mod dirs;
|
|
mod icon;
|
|
mod lock;
|
|
mod models;
|
|
mod oauth;
|
|
mod pipe;
|
|
mod state_machine;
|
|
|
|
use crate::config::Config;
|
|
use clap::Parser;
|
|
use lock::Lock;
|
|
use oauth::{start_code_listener, start_code_to_token};
|
|
use pipe::Pipe;
|
|
use state_machine::Event;
|
|
use tokio::{sync::broadcast::channel, task::JoinSet};
|
|
use tracing::Level;
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, Layer};
|
|
|
|
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";
|
|
pub static CLIENT_ID: uuid::Uuid = uuid::uuid!("f9525060-0a34-4233-87e2-0f9990b7c6db");
|
|
pub static REDIRECT_URI: &str = "avam:token";
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(version, about, long_about = None)]
|
|
pub struct Arguments {
|
|
#[arg(short, long)]
|
|
code: Option<String>,
|
|
#[arg(short, long, action)]
|
|
force: bool,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), anyhow::Error> {
|
|
dotenvy::dotenv().ok();
|
|
|
|
init_logging()?;
|
|
|
|
let (event_sender, event_receiver) = channel(10);
|
|
let args = Arguments::parse();
|
|
|
|
if handle_single_instance(&args).await? {
|
|
return Ok(());
|
|
}
|
|
|
|
register_url_scheme()?;
|
|
register_notification_handler()?;
|
|
|
|
let config = Config::new()?;
|
|
|
|
let mut futures = JoinSet::new();
|
|
|
|
// Register Quit handler
|
|
let sender = event_sender.clone();
|
|
let mut ctrl_c_counter = 0;
|
|
ctrlc::set_handler(move || {
|
|
tracing::info!("CTRL_C: Quit singal sent");
|
|
ctrl_c_counter += 1;
|
|
if ctrl_c_counter >= 3 {
|
|
let _ = unregister_url_scheme();
|
|
Lock::unlock();
|
|
std::process::exit(1);
|
|
}
|
|
|
|
if let Err(e) = sender.send(Event::Quit) {
|
|
tracing::error!("{:#?}", e)
|
|
};
|
|
})?;
|
|
|
|
// Start the State Machine
|
|
let c = config.clone();
|
|
let sender = event_sender.clone();
|
|
let receiver = event_receiver.resubscribe();
|
|
futures.spawn(state_machine::start(c, sender, receiver));
|
|
|
|
// // Start the code listener
|
|
let receiver = event_receiver.resubscribe();
|
|
let (pipe_sender, pipe_receiver) = tokio::sync::broadcast::channel(10);
|
|
futures.spawn(start_code_listener(pipe_sender, receiver));
|
|
|
|
// Start token listener
|
|
let c = config.clone();
|
|
let sender = event_sender.clone();
|
|
let receiver = event_receiver.resubscribe();
|
|
futures.spawn(start_code_to_token(c, pipe_receiver, sender, receiver));
|
|
|
|
// Prepare channels for socket <-> simconnect
|
|
let (simconnect_sender, __simconnect_receiver) = channel(10);
|
|
let (__socket_sender, socket_receiver) = channel(10);
|
|
|
|
// Start the websocket client
|
|
let c = config.clone();
|
|
let sender = event_sender.clone();
|
|
let receiver = event_receiver.resubscribe();
|
|
futures.spawn(client::start(
|
|
c,
|
|
simconnect_sender,
|
|
socket_receiver,
|
|
sender,
|
|
receiver,
|
|
));
|
|
|
|
// Start the simconnect listener
|
|
// The simconnect sends data to the webscoket
|
|
// It also receives data from the websocket to do things like set plane id and fuel and such things
|
|
// If possible even position
|
|
|
|
// Start the Tray Icon
|
|
let c = config.clone();
|
|
let sender = event_sender.clone();
|
|
let receiver = event_receiver.resubscribe();
|
|
app::start(c, sender, receiver).await?;
|
|
|
|
// Wait for everything to finish
|
|
while let Some(result) = futures.join_next().await {
|
|
if let Ok(Err(e)) = result {
|
|
panic!("{:#?}", e);
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
unregister_url_scheme()?;
|
|
unregister_notification_handler()?;
|
|
Lock::unlock();
|
|
|
|
Ok(())
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
//
|
|
|
|
/// returns `Ok(true)` if we need to quit gracefully
|
|
/// returns `Err(e)` if we're already running
|
|
async fn handle_single_instance(args: &Arguments) -> Result<bool, anyhow::Error> {
|
|
let lock = Lock::new(args.force)?;
|
|
|
|
if lock.is_locked() && args.code.is_none() {
|
|
return Err(anyhow::anyhow!("Lockfile exists, exiting."));
|
|
}
|
|
|
|
if let Some(code) = &args.code {
|
|
Pipe::send(code).await?;
|
|
return Ok(true);
|
|
}
|
|
|
|
Lock::lock();
|
|
Ok(false)
|
|
}
|
|
|
|
fn register_url_scheme() -> Result<(), anyhow::Error> {
|
|
use winreg::enums::*;
|
|
use winreg::RegKey;
|
|
|
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
|
let avam_schema_root = hkcu.create_subkey("Software\\Classes\\avam")?;
|
|
avam_schema_root.0.set_value("URL Protocol", &"")?;
|
|
let command = avam_schema_root.0.create_subkey("shell\\open\\command")?;
|
|
|
|
let current_exec = std::env::current_exe()?;
|
|
|
|
command.0.set_value(
|
|
"",
|
|
&format!("\"{}\" -c \"%1\"", current_exec.to_str().unwrap()),
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn unregister_url_scheme() -> Result<(), anyhow::Error> {
|
|
use winreg::enums::*;
|
|
use winreg::RegKey;
|
|
|
|
let hkcu = RegKey::predef(HKEY_CURRENT_USER);
|
|
hkcu.delete_subkey_all("Software\\Classes\\avam").ok();
|
|
|
|
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().with_filter(tracing_subscriber::filter::filter_fn(
|
|
|metadata| metadata.level() < &Level::TRACE,
|
|
));
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
let fmt = fmt.with_ansi(false).with_writer(Arc::new(file));
|
|
|
|
tracing_subscriber::registry().with(fmt).init();
|
|
|
|
Ok(())
|
|
}
|