use huddle_core::app::events::AppEvent;
use huddle_core::app::AppHandle;
use huddle_core::network::NetworkMode;
use libp2p::Multiaddr;
use std::future::Future;
use std::path::PathBuf;
pub enum Inbox {
Event(AppEvent),
Lagged(u64),
CmdError(String),
ReqOk(ReqTag, ReqOk),
ReqErr(ReqTag, String),
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub enum ReqTag {
StartRoom,
StartDirect,
JoinCode { room_id: String, room_name: String },
SaveDownload { file_id: String },
SasStart { room_id: String, partner: String },
SendFile,
GoDark,
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub enum ReqOk {
RoomId(String),
JoinCode(String),
SavedPath(PathBuf),
TxId(String),
FileId(String),
Unit,
}
pub enum AuthChoice {
NoPassphrase,
Passphrase(String),
}
pub struct BuildParams {
pub explicit_mode: Option<NetworkMode>,
pub port: u16,
pub relays: Vec<Multiaddr>,
pub server_url: Option<String>,
pub tor_socks: Option<String>,
pub auth: AuthChoice,
pub name: Option<String>,
}
pub struct ReadyParts {
pub handle: AppHandle,
pub inbox_rx: crossbeam_channel::Receiver<Inbox>,
pub inbox_tx: crossbeam_channel::Sender<Inbox>,
}
pub fn spawn_build(
rt: &tokio::runtime::Handle,
ctx: egui::Context,
params: BuildParams,
) -> tokio::sync::oneshot::Receiver<Result<ReadyParts, String>> {
let (tx, rx) = tokio::sync::oneshot::channel();
rt.spawn(async move {
let res = build_inner(ctx, params).await;
let _ = tx.send(res);
});
rx
}
async fn build_inner(ctx: egui::Context, params: BuildParams) -> Result<ReadyParts, String> {
use huddle_core::storage::keychain;
let key: Option<[u8; 32]> = match params.auth {
AuthChoice::NoPassphrase => None,
AuthChoice::Passphrase(pass) => {
if !keychain::keychain_salt_path().exists() && huddle_core::config::db_path().exists() {
return Err(format!(
"found an existing database with no keychain salt — it was created \
with --no-master-passphrase and is unencrypted. Re-run with \
--no-master-passphrase, or move {} aside to start fresh.",
huddle_core::config::db_path().display()
));
}
let salt = keychain::load_or_create_salt().map_err(|e| e.to_string())?;
Some(keychain::derive_master_key(&pass, &salt).map_err(|e| e.to_string())?)
}
};
let mode = match params.explicit_mode {
Some(m) => m,
None => {
if AppHandle::peek_mdns_enabled(key.as_ref()).unwrap_or(false) {
NetworkMode::Mdns
} else {
NetworkMode::Server
}
}
};
let transports = huddle_core::app::TransportConfig {
onion_url: params.server_url,
tor_socks: params.tor_socks,
..Default::default()
};
let handle = AppHandle::start_with_options(
mode,
params.port,
key.as_ref(),
params.relays,
transports,
)
.await
.map_err(|e| e.to_string())?;
if let Some(n) = params.name {
let trimmed = n.trim();
if !trimmed.is_empty() {
let _ = handle.set_display_name(Some(trimmed));
}
}
let (tx, rx) = crossbeam_channel::unbounded::<Inbox>();
spawn_event_pump(handle.subscribe(), tx.clone(), ctx);
Ok(ReadyParts {
handle,
inbox_rx: rx,
inbox_tx: tx,
})
}
fn spawn_event_pump(
mut events: tokio::sync::broadcast::Receiver<AppEvent>,
tx: crossbeam_channel::Sender<Inbox>,
ctx: egui::Context,
) {
use tokio::sync::broadcast::error::RecvError;
tokio::spawn(async move {
loop {
match events.recv().await {
Ok(ev) => {
if tx.send(Inbox::Event(ev)).is_err() {
break; }
ctx.request_repaint();
}
Err(RecvError::Lagged(n)) => {
let _ = tx.send(Inbox::Lagged(n));
ctx.request_repaint();
}
Err(RecvError::Closed) => break,
}
}
});
}
#[derive(Clone)]
pub struct Cmd {
rt: tokio::runtime::Handle,
pub handle: AppHandle,
tx: crossbeam_channel::Sender<Inbox>,
ctx: egui::Context,
}
impl Cmd {
pub fn new(
rt: tokio::runtime::Handle,
handle: AppHandle,
tx: crossbeam_channel::Sender<Inbox>,
ctx: egui::Context,
) -> Self {
Self { rt, handle, tx, ctx }
}
#[allow(dead_code)] pub fn fire<F, Fut, E>(&self, f: F)
where
F: FnOnce(AppHandle) -> Fut + Send + 'static,
Fut: Future<Output = Result<(), E>> + Send + 'static,
E: std::fmt::Display + Send + 'static,
{
let handle = self.handle.clone();
let tx = self.tx.clone();
let ctx = self.ctx.clone();
self.rt.spawn(async move {
if let Err(e) = f(handle).await {
let _ = tx.send(Inbox::CmdError(e.to_string()));
ctx.request_repaint();
}
});
}
#[allow(dead_code)] pub fn request<F, Fut>(&self, tag: ReqTag, f: F)
where
F: FnOnce(AppHandle) -> Fut + Send + 'static,
Fut: Future<Output = Result<ReqOk, String>> + Send + 'static,
{
let handle = self.handle.clone();
let tx = self.tx.clone();
let ctx = self.ctx.clone();
self.rt.spawn(async move {
match f(handle).await {
Ok(ok) => {
let _ = tx.send(Inbox::ReqOk(tag, ok));
}
Err(e) => {
let _ = tx.send(Inbox::ReqErr(tag, e));
}
}
ctx.request_repaint();
});
}
}