use std::{collections::BTreeMap, fs};
use gled_core::paths::known_servers_file;
use gled_protocol::{
message::{ClientMessage, ServerMessage},
state::EngineEvent,
};
use gled_server::client::{ClientEvent, ConnectError, RemoteClient, RemoteTarget, server_key};
use gled_ui::link::EngineLink;
#[derive(Debug)]
pub struct RemoteLink {
client: RemoteClient,
url: String,
open: bool,
was_open: bool,
}
impl RemoteLink {
pub fn new(
url: String,
fingerprint: Option<String>,
ctx: egui::Context,
) -> std::io::Result<Self> {
let fingerprint = fingerprint.or_else(|| known_servers().remove(&server_key(&url)));
let client = RemoteClient::start(
RemoteTarget {
url: url.clone(),
fingerprint,
},
format!("{} desktop", env!("CARGO_PKG_VERSION")),
move || ctx.request_repaint(),
)?;
Ok(Self {
client,
url,
open: false,
was_open: false,
})
}
}
impl EngineLink for RemoteLink {
fn send(&mut self, message: ClientMessage) {
self.client.send(message);
}
fn receive(&mut self) -> Vec<ServerMessage> {
let mut messages = Vec::new();
for event in self.client.events() {
match event {
ClientEvent::Message(message) => messages.push(message),
ClientEvent::Connected { fingerprint } => {
self.open = true;
self.was_open = true;
if let Some(fingerprint) = fingerprint {
remember(&server_key(&self.url), fingerprint);
}
}
ClientEvent::Failed(error) => {
self.open = false;
let interruption = self.was_open
&& matches!(error, ConnectError::Lost(_) | ConnectError::Connect { .. });
if !interruption {
messages.push(ServerMessage::Event(EngineEvent::Error(error.to_string())));
}
}
}
}
messages
}
fn is_remote(&self) -> bool {
true
}
fn is_open(&self) -> bool {
self.open
}
fn target(&self) -> Option<String> {
Some(server_key(&self.url))
}
}
fn known_servers() -> BTreeMap<String, String> {
known_servers_file()
.and_then(|path| fs::read(path).ok())
.and_then(|bytes| serde_json::from_slice(&bytes).ok())
.unwrap_or_default()
}
fn remember(server: &str, fingerprint: String) {
let mut known = known_servers();
if known.get(server) == Some(&fingerprint) {
return;
}
known.insert(server.to_owned(), fingerprint);
let Some(path) = known_servers_file() else {
return;
};
let written = path
.parent()
.map_or(Ok(()), fs::create_dir_all)
.and_then(|()| {
serde_json::to_vec_pretty(&known)
.map_err(std::io::Error::other)
.and_then(|json| fs::write(&path, json))
});
if let Err(err) = written {
tracing::warn!(
"Could not remember the server's certificate in {}: {err}",
path.display()
);
}
}