198 lines
6.2 KiB
Rust
198 lines
6.2 KiB
Rust
use std::sync::Arc;
|
|
|
|
use crate::{control, event::EventManager, router::build_router};
|
|
use axum::extract::FromRef;
|
|
use plc_platform_core::{bootstrap, websocket::WebSocketManager};
|
|
use plc_platform_core::{
|
|
config::ServerConfig, connection::ConnectionManager, platform_context::PlatformContext,
|
|
};
|
|
use tokio::sync::mpsc;
|
|
|
|
#[derive(Clone)]
|
|
pub struct AppState {
|
|
pub config: ServerConfig,
|
|
pub platform: PlatformContext,
|
|
pub event_manager: Arc<EventManager>,
|
|
pub control_runtime: Arc<control::runtime::ControlRuntimeStore>,
|
|
}
|
|
|
|
impl FromRef<AppState> for PlatformContext {
|
|
fn from_ref(state: &AppState) -> Self {
|
|
state.platform.clone()
|
|
}
|
|
}
|
|
|
|
pub async fn run() {
|
|
let Some(_single_instance) = bootstrap::init_process(
|
|
"PLCControl.FeederDistributor",
|
|
"Another feeder distributor instance is already running",
|
|
) else {
|
|
return;
|
|
};
|
|
|
|
let config = ServerConfig::from_env("HOST", "0.0.0.0", "PORT", 60309)
|
|
.expect("Failed to load server configuration");
|
|
let builder = bootstrap::bootstrap_platform(&config.database_url)
|
|
.await
|
|
.expect("Failed to bootstrap platform");
|
|
|
|
let control_runtime = Arc::new(control::runtime::ControlRuntimeStore::new());
|
|
let platform = builder.build();
|
|
|
|
let event_manager = Arc::new(EventManager::new(
|
|
platform.pool.clone(),
|
|
Some(platform.ws_manager.clone()),
|
|
));
|
|
|
|
bootstrap::connect_all_enabled_sources(&platform)
|
|
.await
|
|
.expect("Failed to connect enabled sources");
|
|
|
|
let state = AppState {
|
|
config: config.clone(),
|
|
platform,
|
|
event_manager,
|
|
control_runtime: control_runtime.clone(),
|
|
};
|
|
control::engine::start(state.clone(), control_runtime);
|
|
|
|
let app = build_router(state.clone());
|
|
let ui_url = config.local_ui_url();
|
|
let (shutdown_tx, shutdown_rx) = mpsc::channel::<()>(1);
|
|
let rt_handle = tokio::runtime::Handle::current();
|
|
init_tray(ui_url, shutdown_tx.clone(), rt_handle);
|
|
|
|
let connection_manager_for_shutdown = state.platform.connection_manager.clone();
|
|
bootstrap::install_ctrl_c_shutdown(shutdown_tx);
|
|
|
|
bootstrap::serve_app_with_graceful_shutdown(
|
|
&config,
|
|
"feeder distributor",
|
|
app,
|
|
bootstrap::disconnect_all_on_shutdown(
|
|
shutdown_rx,
|
|
connection_manager_for_shutdown,
|
|
"feeder",
|
|
),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
pub fn test_state() -> AppState {
|
|
let database_url = "postgres://plc:plc@localhost/plc_control_test".to_string();
|
|
let pool = sqlx::postgres::PgPoolOptions::new()
|
|
.connect_lazy(&database_url)
|
|
.expect("lazy pool should build");
|
|
let connection_manager = Arc::new(ConnectionManager::new());
|
|
let ws_manager = Arc::new(WebSocketManager::new());
|
|
let event_manager = Arc::new(EventManager::new(pool.clone(), Some(ws_manager.clone())));
|
|
let platform = PlatformContext::new(pool, connection_manager, ws_manager);
|
|
|
|
AppState {
|
|
config: ServerConfig {
|
|
database_url,
|
|
server_host: "127.0.0.1".to_string(),
|
|
server_port: 0,
|
|
},
|
|
platform,
|
|
event_manager,
|
|
control_runtime: Arc::new(control::runtime::ControlRuntimeStore::new()),
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn init_tray(ui_url: String, shutdown_tx: mpsc::Sender<()>, rt_handle: tokio::runtime::Handle) {
|
|
std::thread::spawn(move || {
|
|
if let Err(err) = tray::run_tray(ui_url, shutdown_tx, rt_handle) {
|
|
tracing::warn!("Tray init failed: {}", err);
|
|
}
|
|
});
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
fn init_tray(_ui_url: String, _shutdown_tx: mpsc::Sender<()>, _rt_handle: tokio::runtime::Handle) {}
|
|
|
|
#[cfg(windows)]
|
|
mod tray {
|
|
use std::error::Error;
|
|
|
|
use tokio::sync::mpsc;
|
|
use tray_icon::{
|
|
menu::{Menu, MenuEvent, MenuItem},
|
|
Icon, TrayIconBuilder,
|
|
};
|
|
use winit::application::ApplicationHandler;
|
|
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
|
use winit::platform::windows::EventLoopBuilderExtWindows;
|
|
|
|
pub fn run_tray(
|
|
ui_url: String,
|
|
shutdown_tx: mpsc::Sender<()>,
|
|
rt_handle: tokio::runtime::Handle,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let mut builder = EventLoop::builder();
|
|
builder.with_any_thread(true);
|
|
let event_loop = builder.build()?;
|
|
|
|
let menu = Menu::new();
|
|
let open_item = MenuItem::new("Open UI", true, None);
|
|
let exit_item = MenuItem::new("Exit", true, None);
|
|
menu.append(&open_item)?;
|
|
menu.append(&exit_item)?;
|
|
|
|
let icon = Icon::from_rgba(vec![0, 120, 212, 255], 1, 1)?;
|
|
let _tray = TrayIconBuilder::new()
|
|
.with_tooltip("PLC Feeder Distributor")
|
|
.with_menu(Box::new(menu))
|
|
.with_icon(icon)
|
|
.build()?;
|
|
|
|
let menu_rx = MenuEvent::receiver();
|
|
let mut app = TrayApp {
|
|
menu_rx,
|
|
open_id: open_item.id().clone(),
|
|
exit_id: exit_item.id().clone(),
|
|
ui_url,
|
|
shutdown_tx,
|
|
rt_handle,
|
|
};
|
|
|
|
event_loop.run_app(&mut app).map_err(|err| err.into())
|
|
}
|
|
|
|
struct TrayApp {
|
|
menu_rx: &'static tray_icon::menu::MenuEventReceiver,
|
|
open_id: tray_icon::menu::MenuId,
|
|
exit_id: tray_icon::menu::MenuId,
|
|
ui_url: String,
|
|
shutdown_tx: mpsc::Sender<()>,
|
|
rt_handle: tokio::runtime::Handle,
|
|
}
|
|
|
|
impl ApplicationHandler for TrayApp {
|
|
fn resumed(&mut self, _event_loop: &ActiveEventLoop) {}
|
|
|
|
fn window_event(
|
|
&mut self,
|
|
_event_loop: &ActiveEventLoop,
|
|
_window_id: winit::window::WindowId,
|
|
_event: winit::event::WindowEvent,
|
|
) {
|
|
}
|
|
|
|
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
|
event_loop.set_control_flow(ControlFlow::Wait);
|
|
while let Ok(menu_event) = self.menu_rx.try_recv() {
|
|
if menu_event.id == self.open_id {
|
|
let _ = webbrowser::open(&self.ui_url);
|
|
}
|
|
if menu_event.id == self.exit_id {
|
|
let _ = self.rt_handle.block_on(self.shutdown_tx.send(()));
|
|
event_loop.exit();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|