Files
doctate/clients/desktop/src/main.rs
T
Brummel 0c66fc6010 refactor: rename to common/, clients/desktop, clients/wearos
Move three directories into their target topology:
- doctate-common/ -> common/
- client-desktop/ -> clients/desktop/
- watch/wearos/   -> clients/wearos/

Update doctate-common path-dependency in 3 Cargo.toml files
(server, clients/desktop, experiments) and the workspace
members list in the root Cargo.toml. Crate names unchanged
(doctate-server, doctate-common, doctate-desktop).

Workspace still in single-Cargo.lock form; isolation into
standalone workspaces follows in stage 3. All 508 tests pass,
experiments standalone-workspace also resolves the new path.
2026-05-02 11:55:38 +02:00

113 lines
3.5 KiB
Rust

use std::sync::Arc;
use eframe::egui;
use tracing::info;
use tracing_subscriber::EnvFilter;
use std::fs::{File, OpenOptions, TryLockError};
use doctate_desktop::app::DoctateApp;
use doctate_desktop::paths::{cases_dir, lock_file_path, pending_dir, snapshot_cache_path};
/// Unwrap a startup `Result` or print `context: {err}` to stderr and
/// `exit(1)`. Used for unrecoverable bootstrap failures before the
/// egui event loop runs — there is no UI to show an error in.
fn or_die<T, E: std::fmt::Display>(result: Result<T, E>, context: &str) -> T {
match result {
Ok(v) => v,
Err(e) => {
eprintln!("{context}: {e}");
std::process::exit(1);
}
}
}
fn main() -> eframe::Result {
tracing_subscriber::fmt()
.with_env_filter(
EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("doctate_desktop=info")),
)
.init();
info!("starting doctate-desktop");
let runtime = Arc::new(
tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.expect("build tokio runtime"),
);
let pending = or_die(pending_dir(), "cannot determine pending directory");
// Single-instance guard. The lock is held by the `_lock_file` binding
// for the entire `main` lifetime — OS releases it automatically on
// process exit or crash, so a stale lock file after a crash is
// harmless (the next launch re-acquires).
let _lock_file = acquire_single_instance_lock();
let cases = or_die(cases_dir(), "cannot determine cases directory");
let cache_path = or_die(
snapshot_cache_path(),
"cannot determine snapshot cache path",
);
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([400.0, 560.0])
.with_min_inner_size([320.0, 400.0])
.with_resizable(true),
..Default::default()
};
eframe::run_native(
"doctate",
options,
Box::new(move |_cc| {
let app = DoctateApp::new(runtime, pending, cases, cache_path);
Ok(Box::new(app) as Box<dyn eframe::App>)
}),
)
}
/// Open and exclusively lock a per-user `client.lock`. Returns the
/// handle; dropping it (process exit) releases the lock. If another
/// instance already holds it, prints a friendly message and exits.
fn acquire_single_instance_lock() -> File {
let path = or_die(lock_file_path(), "cannot determine lock file path");
if let Some(parent) = path.parent()
&& let Err(e) = std::fs::create_dir_all(parent)
{
eprintln!("cannot create data directory {}: {e}", parent.display());
std::process::exit(1);
}
let file = match OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(&path)
{
Ok(f) => f,
Err(e) => {
eprintln!("cannot open lock file {}: {e}", path.display());
std::process::exit(1);
}
};
match file.try_lock() {
Ok(()) => file,
Err(TryLockError::WouldBlock) => {
eprintln!(
"doctate läuft bereits (Lock hält auf {}). Nur eine Instanz pro User zulässig.",
path.display()
);
std::process::exit(1);
}
Err(TryLockError::Error(e)) => {
eprintln!("lock acquire failed on {}: {e}", path.display());
std::process::exit(1);
}
}
}