bind network connection to remotes record changes
This commit is contained in:
+166
-45
@@ -1,4 +1,5 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -15,9 +16,9 @@ use wyd_common::{
|
|||||||
|
|
||||||
use crate::db::AppDatabase;
|
use crate::db::AppDatabase;
|
||||||
use crate::keypair::AppKeypair;
|
use crate::keypair::AppKeypair;
|
||||||
use crate::remotes::{self, Remote};
|
use crate::remotes::{self, Remote, RemotesChanged};
|
||||||
|
|
||||||
type Statuses = Arc<Mutex<HashMap<String, ConnectionStatus>>>;
|
type Statuses = Arc<Mutex<HashMap<String, (u64, ConnectionStatus)>>>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Type)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@@ -42,18 +43,28 @@ pub struct NetworkStatusChanged {
|
|||||||
pub statuses: Vec<ConnectionStatus>,
|
pub statuses: Vec<ConnectionStatus>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Connection {
|
||||||
|
remote: Remote,
|
||||||
|
sender: mpsc::Sender<String>,
|
||||||
|
task: tauri::async_runtime::JoinHandle<()>,
|
||||||
|
generation: u64,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Network {
|
pub struct Network {
|
||||||
senders: Mutex<HashMap<String, mpsc::Sender<String>>>,
|
connections: Mutex<HashMap<String, Connection>>,
|
||||||
statuses: Statuses,
|
statuses: Statuses,
|
||||||
profile: watch::Sender<crate::user::User>,
|
profile: watch::Sender<crate::user::User>,
|
||||||
|
keypair: AppKeypair,
|
||||||
|
next_generation: AtomicU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Network {
|
impl Network {
|
||||||
#[allow(dead_code)] // Ready for the first domain message sender.
|
#[allow(dead_code)] // Ready for the first domain message sender.
|
||||||
pub fn send(&self, remote_id: &str, payload: String) -> Result<(), String> {
|
pub fn send(&self, remote_id: &str, payload: String) -> Result<(), String> {
|
||||||
let senders = self.senders.lock().map_err(|error| error.to_string())?;
|
let connections = self.connections.lock().map_err(|error| error.to_string())?;
|
||||||
let sender = senders
|
let sender = connections
|
||||||
.get(remote_id)
|
.get(remote_id)
|
||||||
|
.map(|connection| &connection.sender)
|
||||||
.ok_or_else(|| "remote is not configured".to_string())?;
|
.ok_or_else(|| "remote is not configured".to_string())?;
|
||||||
sender
|
sender
|
||||||
.try_send(payload)
|
.try_send(payload)
|
||||||
@@ -63,35 +74,86 @@ impl Network {
|
|||||||
pub fn update_profile(&self, profile: crate::user::User) {
|
pub fn update_profile(&self, profile: crate::user::User) {
|
||||||
self.profile.send_replace(profile);
|
self.profile.send_replace(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_remotes(&self, handle: &AppHandle, remotes: Vec<Remote>) -> Result<(), String> {
|
||||||
|
let desired: HashMap<_, _> = remotes
|
||||||
|
.into_iter()
|
||||||
|
.map(|remote| (remote.id.clone(), remote))
|
||||||
|
.collect();
|
||||||
|
let mut connections = self.connections.lock().map_err(|error| error.to_string())?;
|
||||||
|
|
||||||
|
let stale: Vec<_> = connections
|
||||||
|
.iter()
|
||||||
|
.filter(|(id, connection)| desired.get(*id) != Some(&connection.remote))
|
||||||
|
.map(|(id, _)| id.clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for id in stale {
|
||||||
|
if let Some(connection) = connections.remove(&id) {
|
||||||
|
connection.task.abort();
|
||||||
|
remove_status(&self.statuses, &id, connection.generation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for remote in desired.into_values() {
|
||||||
|
if connections.contains_key(&remote.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let generation = self.next_generation.fetch_add(1, Ordering::Relaxed);
|
||||||
|
let (sender, receiver) = mpsc::channel(32);
|
||||||
|
set_initial(
|
||||||
|
&self.statuses,
|
||||||
|
&remote,
|
||||||
|
generation,
|
||||||
|
ConnectionState::Connecting,
|
||||||
|
);
|
||||||
|
let task = tauri::async_runtime::spawn(run(
|
||||||
|
handle.clone(),
|
||||||
|
self.statuses.clone(),
|
||||||
|
remote.clone(),
|
||||||
|
generation,
|
||||||
|
self.profile.subscribe(),
|
||||||
|
self.keypair.clone(),
|
||||||
|
receiver,
|
||||||
|
));
|
||||||
|
connections.insert(
|
||||||
|
remote.id.clone(),
|
||||||
|
Connection {
|
||||||
|
remote,
|
||||||
|
sender,
|
||||||
|
task,
|
||||||
|
generation,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(connections);
|
||||||
|
emit_statuses(handle, &self.statuses)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init(handle: &AppHandle) -> Result<(), Box<dyn std::error::Error>> {
|
pub async fn init(handle: &AppHandle) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let database = handle.state::<AppDatabase>();
|
let database = handle.state::<AppDatabase>();
|
||||||
let keypair = handle.state::<AppKeypair>().inner().clone();
|
let keypair = handle.state::<AppKeypair>().inner().clone();
|
||||||
let profile = crate::profile::get(&database, keypair.public_key()).await?;
|
let profile = crate::profile::get(&database, keypair.public_key()).await?;
|
||||||
let (profile_sender, profile_receiver) = watch::channel(profile);
|
let (profile, _) = watch::channel(profile);
|
||||||
let remotes = remotes::all(&database).await?;
|
let network = Network {
|
||||||
let mut senders = HashMap::new();
|
connections: Mutex::new(HashMap::new()),
|
||||||
let statuses = Statuses::default();
|
statuses: Statuses::default(),
|
||||||
|
profile,
|
||||||
|
keypair,
|
||||||
|
next_generation: AtomicU64::new(1),
|
||||||
|
};
|
||||||
|
network.sync_remotes(handle, remotes::all(&database).await?)?;
|
||||||
|
handle.manage(network);
|
||||||
|
|
||||||
for remote in remotes {
|
let listener_handle = handle.clone();
|
||||||
let (sender, receiver) = mpsc::channel(32);
|
RemotesChanged::listen(handle, move |event| {
|
||||||
senders.insert(remote.id.clone(), sender);
|
let network = listener_handle.state::<Network>();
|
||||||
set(&statuses, &remote, ConnectionState::Connecting);
|
if let Err(error) = network.sync_remotes(&listener_handle, event.payload.remotes) {
|
||||||
tauri::async_runtime::spawn(run(
|
eprintln!("failed to synchronize remote connections: {error}");
|
||||||
handle.clone(),
|
}
|
||||||
statuses.clone(),
|
|
||||||
remote,
|
|
||||||
profile_receiver.clone(),
|
|
||||||
keypair.clone(),
|
|
||||||
receiver,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
handle.manage(Network {
|
|
||||||
senders: Mutex::new(senders),
|
|
||||||
statuses,
|
|
||||||
profile: profile_sender,
|
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -100,16 +162,24 @@ async fn run(
|
|||||||
handle: AppHandle,
|
handle: AppHandle,
|
||||||
statuses: Statuses,
|
statuses: Statuses,
|
||||||
remote: Remote,
|
remote: Remote,
|
||||||
|
generation: u64,
|
||||||
mut profiles: watch::Receiver<crate::user::User>,
|
mut profiles: watch::Receiver<crate::user::User>,
|
||||||
keypair: AppKeypair,
|
keypair: AppKeypair,
|
||||||
mut outgoing: mpsc::Receiver<String>,
|
mut outgoing: mpsc::Receiver<String>,
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
changed(&handle, &statuses, &remote, ConnectionState::Connecting);
|
changed(
|
||||||
|
&handle,
|
||||||
|
&statuses,
|
||||||
|
&remote,
|
||||||
|
generation,
|
||||||
|
ConnectionState::Connecting,
|
||||||
|
);
|
||||||
if let Err(error) = connect(
|
if let Err(error) = connect(
|
||||||
&handle,
|
&handle,
|
||||||
&statuses,
|
&statuses,
|
||||||
&remote,
|
&remote,
|
||||||
|
generation,
|
||||||
&mut profiles,
|
&mut profiles,
|
||||||
&keypair,
|
&keypair,
|
||||||
&mut outgoing,
|
&mut outgoing,
|
||||||
@@ -118,7 +188,13 @@ async fn run(
|
|||||||
{
|
{
|
||||||
eprintln!("remote {} disconnected: {error}", remote.id);
|
eprintln!("remote {} disconnected: {error}", remote.id);
|
||||||
}
|
}
|
||||||
changed(&handle, &statuses, &remote, ConnectionState::Disconnected);
|
changed(
|
||||||
|
&handle,
|
||||||
|
&statuses,
|
||||||
|
&remote,
|
||||||
|
generation,
|
||||||
|
ConnectionState::Disconnected,
|
||||||
|
);
|
||||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,6 +203,7 @@ async fn connect(
|
|||||||
handle: &AppHandle,
|
handle: &AppHandle,
|
||||||
statuses: &Statuses,
|
statuses: &Statuses,
|
||||||
remote: &Remote,
|
remote: &Remote,
|
||||||
|
generation: u64,
|
||||||
profiles: &mut watch::Receiver<crate::user::User>,
|
profiles: &mut watch::Receiver<crate::user::User>,
|
||||||
keypair: &AppKeypair,
|
keypair: &AppKeypair,
|
||||||
outgoing: &mut mpsc::Receiver<String>,
|
outgoing: &mut mpsc::Receiver<String>,
|
||||||
@@ -157,7 +234,13 @@ async fn connect(
|
|||||||
if !matches!(recv(&mut reader).await?, ServerMessage::Registered) {
|
if !matches!(recv(&mut reader).await?, ServerMessage::Registered) {
|
||||||
return Err("server rejected registration".into());
|
return Err("server rejected registration".into());
|
||||||
}
|
}
|
||||||
changed(handle, statuses, remote, ConnectionState::Connected);
|
changed(
|
||||||
|
handle,
|
||||||
|
statuses,
|
||||||
|
remote,
|
||||||
|
generation,
|
||||||
|
ConnectionState::Connected,
|
||||||
|
);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
@@ -204,33 +287,71 @@ pub fn list_statuses(
|
|||||||
Ok(statuses)
|
Ok(statuses)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn changed(handle: &AppHandle, statuses: &Statuses, remote: &Remote, state: ConnectionState) {
|
fn changed(
|
||||||
set(statuses, remote, state);
|
handle: &AppHandle,
|
||||||
if let Ok(statuses) = snapshot(statuses) {
|
statuses: &Statuses,
|
||||||
let _ = NetworkStatusChanged { statuses }.emit(handle);
|
remote: &Remote,
|
||||||
|
generation: u64,
|
||||||
|
state: ConnectionState,
|
||||||
|
) {
|
||||||
|
if set(statuses, remote, generation, state) {
|
||||||
|
let _ = emit_statuses(handle, statuses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(statuses: &Statuses, remote: &Remote, state: ConnectionState) {
|
fn set_initial(statuses: &Statuses, remote: &Remote, generation: u64, state: ConnectionState) {
|
||||||
if let Ok(mut statuses) = statuses.lock() {
|
if let Ok(mut statuses) = statuses.lock() {
|
||||||
statuses.insert(
|
statuses.insert(remote.id.clone(), (generation, status(remote, state)));
|
||||||
remote.id.clone(),
|
|
||||||
ConnectionStatus {
|
|
||||||
remote_id: remote.id.clone(),
|
|
||||||
address: remote.address.clone(),
|
|
||||||
name: remote.name.clone(),
|
|
||||||
state,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set(statuses: &Statuses, remote: &Remote, generation: u64, state: ConnectionState) -> bool {
|
||||||
|
let Ok(mut statuses) = statuses.lock() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some((current_generation, current)) = statuses.get_mut(&remote.id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if *current_generation != generation {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*current = status(remote, state);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_status(statuses: &Statuses, remote_id: &str, generation: u64) {
|
||||||
|
if let Ok(mut statuses) = statuses.lock()
|
||||||
|
&& statuses
|
||||||
|
.get(remote_id)
|
||||||
|
.is_some_and(|(current, _)| *current == generation)
|
||||||
|
{
|
||||||
|
statuses.remove(remote_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(remote: &Remote, state: ConnectionState) -> ConnectionStatus {
|
||||||
|
ConnectionStatus {
|
||||||
|
remote_id: remote.id.clone(),
|
||||||
|
address: remote.address.clone(),
|
||||||
|
name: remote.name.clone(),
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_statuses(handle: &AppHandle, statuses: &Statuses) -> Result<(), String> {
|
||||||
|
NetworkStatusChanged {
|
||||||
|
statuses: snapshot(statuses)?,
|
||||||
|
}
|
||||||
|
.emit(handle)
|
||||||
|
.map_err(|error| error.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn snapshot(statuses: &Statuses) -> Result<Vec<ConnectionStatus>, String> {
|
fn snapshot(statuses: &Statuses) -> Result<Vec<ConnectionStatus>, String> {
|
||||||
let mut statuses: Vec<_> = statuses
|
let mut statuses: Vec<_> = statuses
|
||||||
.lock()
|
.lock()
|
||||||
.map_err(|error| error.to_string())?
|
.map_err(|error| error.to_string())?
|
||||||
.values()
|
.values()
|
||||||
.cloned()
|
.map(|(_, status)| status.clone())
|
||||||
.collect();
|
.collect();
|
||||||
statuses.sort_by(|a, b| a.remote_id.cmp(&b.remote_id));
|
statuses.sort_by(|a, b| a.remote_id.cmp(&b.remote_id));
|
||||||
Ok(statuses)
|
Ok(statuses)
|
||||||
|
|||||||
Reference in New Issue
Block a user