Update dependencies and enhance WebSocket server logging: Add 'chrono' and 'android_system_properties' to Cargo.lock, improve error handling and logging in websocket_server.rs, and streamline character creation notifications in worker modules for better clarity and maintainability.

This commit is contained in:
Torsten Schulz (local)
2026-01-28 14:21:28 +01:00
parent 2ac474fe0c
commit c9e0781b61
14 changed files with 1174 additions and 1814 deletions

View File

@@ -1,7 +1,6 @@
use crate::db::{Row, Rows};
use crate::message_broker::MessageBroker;
use std::cmp::min;
use std::sync::Mutex;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::{Duration, Instant};
@@ -14,12 +13,8 @@ use crate::worker::sql::{
QUERY_DELETE_PRODUCTION,
QUERY_INSERT_INVENTORY,
QUERY_INSERT_UPDATE_PRODUCTION_LOG,
QUERY_SET_PRODUCTION_SLEEP,
QUERY_GET_SLEEP_PRODUCTIONS,
QUERY_ADD_OVERPRODUCTION_NOTIFICATION,
};
use crate::worker::insert_notification_conn;
const QUERY_COUNT_OPEN_PRODUCTIONS: &str = r#"SELECT COUNT(*) AS cnt FROM falukant_data.production;"#;
/// Abbildet eine abgeschlossene Produktion aus der Datenbank.
#[derive(Debug, Clone)]
@@ -45,8 +40,6 @@ struct StockInfo {
pub struct ProduceWorker {
base: BaseWorker,
last_iteration: Option<Instant>,
last_start_fix: Option<Instant>,
last_sleep_check: Option<Instant>,
}
impl ProduceWorker {
@@ -54,8 +47,6 @@ impl ProduceWorker {
Self {
base: BaseWorker::new("ProduceWorker", pool, broker),
last_iteration: None,
last_start_fix: None,
last_sleep_check: None,
}
}
@@ -76,8 +67,6 @@ impl ProduceWorker {
self.base.set_current_step("Process Productions");
self.process_productions();
self.base.set_current_step("Process Sleep Productions");
self.process_sleep_productions();
self.base.set_current_step("Signal Activity");
// TODO: Später Analogie zu signalActivity() aus der C++-Basisklasse herstellen.
self.base.set_current_step("Loop Done");
@@ -123,20 +112,6 @@ impl ProduceWorker {
}
fn process_productions(&mut self) {
// Heartbeat (damit sichtbar ist, dass der Worker läuft), max. 1x/Stunde.
static LAST_HEARTBEAT: Mutex<Option<Instant>> = Mutex::new(None);
{
let mut last = LAST_HEARTBEAT.lock().unwrap();
let should_log = last
.map(|t| t.elapsed().as_secs() >= 3600)
.unwrap_or(true);
if should_log {
eprintln!("[ProduceWorker] Heartbeat: alive");
*last = Some(Instant::now());
}
}
self.maybe_fix_null_production_starts();
self.base
.set_current_step("Fetch Finished Productions");
@@ -148,50 +123,6 @@ impl ProduceWorker {
}
};
// Debug: Gedrosselt loggen, wenn nie etwas "fertig" wird.
if finished_productions.is_empty() {
static LAST_EMPTY_LOG: Mutex<Option<Instant>> = Mutex::new(None);
let mut last = LAST_EMPTY_LOG.lock().unwrap();
let should_log = last
.map(|t| t.elapsed().as_secs() >= 60)
.unwrap_or(true);
if should_log {
let mut conn = match self.base.pool.get() {
Ok(c) => c,
Err(e) => {
eprintln!("[ProduceWorker] DB-Verbindung fehlgeschlagen (debug): {e}");
return;
}
};
let open_cnt = (|| -> Result<i32, crate::db::DbError> {
conn.prepare("count_open_productions", QUERY_COUNT_OPEN_PRODUCTIONS)?;
let rows = conn.execute("count_open_productions", &[])?;
Ok(rows
.first()
.and_then(|r| r.get("cnt"))
.and_then(|v| v.parse::<i32>().ok())
.unwrap_or(0))
})()
.unwrap_or(0);
let mut sql_preview = QUERY_GET_FINISHED_PRODUCTIONS.to_string();
const MAX_SQL_PREVIEW: usize = 1200;
if sql_preview.len() > MAX_SQL_PREVIEW {
sql_preview.truncate(MAX_SQL_PREVIEW);
sql_preview.push_str("");
}
eprintln!(
"[ProduceWorker] Keine fertigen Produktionen gefunden. Offene Produktionen in DB: {}. Query(get_finished_productions): {}",
open_cnt,
sql_preview
);
*last = Some(Instant::now());
}
}
self.base
.set_current_step("Process Finished Productions");
@@ -221,37 +152,12 @@ impl ProduceWorker {
production_id,
} = *production;
// Prüfe VOR dem Abschluss, ob genug Lagerplatz vorhanden ist
if self.has_enough_storage_capacity(branch_id, quantity) {
// Genug Platz: Produktion abschließen
if self.add_to_inventory(branch_id, product_id, quantity, quality, user_id) {
self.delete_production(production_id);
self.add_production_to_log(region_id, user_id, product_id, quantity);
}
} else {
// Nicht genug Platz: Produktion auf sleep setzen
if let Err(err) = self.set_production_sleep(production_id) {
eprintln!("[ProduceWorker] Fehler beim Setzen von sleep für Produktion {}: {}", production_id, err);
}
if self.add_to_inventory(branch_id, product_id, quantity, quality, user_id) {
self.delete_production(production_id);
self.add_production_to_log(region_id, user_id, product_id, quantity);
}
}
fn has_enough_storage_capacity(&self, branch_id: i32, required_quantity: i32) -> bool {
let stocks = match self.get_available_stocks(branch_id) {
Ok(rows) => rows,
Err(err) => {
eprintln!("[ProduceWorker] Fehler in getAvailableStocks: {err}");
return false;
}
};
let total_free_capacity: i32 = stocks.iter()
.map(|stock| (stock.total_capacity - stock.filled).max(0))
.sum();
total_free_capacity >= required_quantity
}
fn add_to_inventory(
&mut self,
branch_id: i32,
@@ -395,37 +301,6 @@ impl ProduceWorker {
conn.execute("get_finished_productions", &[])
}
fn maybe_fix_null_production_starts(&mut self) {
let now = Instant::now();
let should_run = self
.last_start_fix
.map(|t| now.saturating_duration_since(t) >= Duration::from_secs(3600))
.unwrap_or(true);
if !should_run {
return;
}
self.last_start_fix = Some(now);
let mut conn = match self.base.pool.get() {
Ok(c) => c,
Err(e) => {
eprintln!("[ProduceWorker] DB-Verbindung fehlgeschlagen (start_fix): {e}");
return;
}
};
// best-effort: nur loggen, wenn es scheitert
use crate::worker::sql::QUERY_FIX_NULL_PRODUCTION_START;
if let Err(err) = conn.prepare("fix_null_production_start", QUERY_FIX_NULL_PRODUCTION_START) {
eprintln!("[ProduceWorker] Fehler prepare fix_null_production_start: {err}");
return;
}
if let Err(err) = conn.execute("fix_null_production_start", &[]) {
eprintln!("[ProduceWorker] Fehler exec fix_null_production_start: {err}");
}
}
fn load_available_stocks(&self, branch_id: i32) -> Result<Rows, crate::db::DbError> {
let mut conn = self
.base
@@ -489,95 +364,6 @@ impl ProduceWorker {
Ok(())
}
fn set_production_sleep(&self, production_id: i32) -> Result<(), crate::db::DbError> {
let mut conn = self
.base
.pool
.get()
.map_err(|e| crate::db::DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("set_production_sleep", QUERY_SET_PRODUCTION_SLEEP)?;
conn.execute("set_production_sleep", &[&production_id])?;
Ok(())
}
fn process_sleep_productions(&mut self) {
const SLEEP_CHECK_INTERVAL_SECS: u64 = 300; // 5 Minuten
let now = Instant::now();
let should_check = match self.last_sleep_check {
None => {
self.last_sleep_check = Some(now);
true
}
Some(last) => {
if now.saturating_duration_since(last).as_secs() >= SLEEP_CHECK_INTERVAL_SECS {
self.last_sleep_check = Some(now);
true
} else {
false
}
}
};
if !should_check {
return;
}
let sleep_productions = match self.get_sleep_productions() {
Ok(rows) => rows,
Err(err) => {
eprintln!("[ProduceWorker] Fehler beim Laden von sleep-Produktionen: {err}");
Vec::new()
}
};
for production in sleep_productions {
self.handle_sleep_production(&production);
}
}
fn get_sleep_productions(&self) -> Result<Vec<FinishedProduction>, crate::db::DbError> {
let rows = self.load_sleep_productions()?;
Ok(rows
.into_iter()
.filter_map(Self::map_row_to_finished_production)
.collect())
}
fn load_sleep_productions(&self) -> Result<Rows, crate::db::DbError> {
let mut conn = self
.base
.pool
.get()
.map_err(|e| crate::db::DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare("get_sleep_productions", QUERY_GET_SLEEP_PRODUCTIONS)?;
conn.execute("get_sleep_productions", &[])
}
fn handle_sleep_production(&mut self, production: &FinishedProduction) {
let FinishedProduction {
branch_id,
product_id,
quantity,
quality,
user_id,
region_id,
production_id,
} = *production;
// Prüfe erneut, ob jetzt genug Lagerplatz vorhanden ist
if self.has_enough_storage_capacity(branch_id, quantity) {
// Jetzt genug Platz: Produktion abschließen
if self.add_to_inventory(branch_id, product_id, quantity, quality, user_id) {
self.delete_production(production_id);
self.add_production_to_log(region_id, user_id, product_id, quantity);
}
}
// Wenn immer noch nicht genug Platz: Produktion bleibt auf sleep
}
fn insert_or_update_production_log(
&self,
region_id: i32,
@@ -616,13 +402,22 @@ impl ProduceWorker {
.get()
.map_err(|e| crate::db::DbError::new(format!("DB-Verbindung fehlgeschlagen: {e}")))?;
conn.prepare(
"add_overproduction_notification",
QUERY_ADD_OVERPRODUCTION_NOTIFICATION,
)?;
// Zusätzlich zur Menge die Branch-ID in der Payload mitschicken, damit
// das Frontend die Überproduktion einem konkreten Branch zuordnen kann.
let notification = format!(
r#"{{"tr":"production.overproduction","value":{},"branch_id":{}}}"#,
remaining_quantity, branch_id
);
insert_notification_conn(&mut conn, user_id, &notification, None)?;
conn.execute(
"add_overproduction_notification",
&[&user_id, &notification],
)?;
Ok(())
}