mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-07 11:59:59 +03:00
extract frontend crate (#186)
This commit is contained in:
committed by
GitHub
parent
12bc0d86ca
commit
be677d4c81
16
lan-mouse-ipc/Cargo.toml
Normal file
16
lan-mouse-ipc/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "lan-mouse-ipc"
|
||||
description = "library for communication between lan-mouse service and frontends"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
repository = "https://github.com/feschber/lan-mouse"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.30"
|
||||
log = "0.4.22"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
thiserror = "1.0.63"
|
||||
tokio = { version = "1.32.0", features = ["net", "io-util", "time"] }
|
||||
tokio-stream = { version = "0.1.15", features = ["io-util"] }
|
||||
89
lan-mouse-ipc/src/connect.rs
Normal file
89
lan-mouse-ipc/src/connect.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use crate::{ConnectionError, FrontendEvent, FrontendRequest, IpcError};
|
||||
use std::{
|
||||
cmp::min,
|
||||
io::{self, prelude::*, BufReader, LineWriter, Lines},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::net::UnixStream;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::net::TcpStream;
|
||||
|
||||
pub struct FrontendEventReader {
|
||||
#[cfg(unix)]
|
||||
lines: Lines<BufReader<UnixStream>>,
|
||||
#[cfg(windows)]
|
||||
lines: Lines<BufReader<TcpStream>>,
|
||||
}
|
||||
|
||||
pub struct FrontendRequestWriter {
|
||||
#[cfg(unix)]
|
||||
line_writer: LineWriter<UnixStream>,
|
||||
#[cfg(windows)]
|
||||
line_writer: LineWriter<TcpStream>,
|
||||
}
|
||||
|
||||
impl FrontendEventReader {
|
||||
pub fn next_event(&mut self) -> Option<Result<FrontendEvent, IpcError>> {
|
||||
match self.lines.next()? {
|
||||
Err(e) => Some(Err(e.into())),
|
||||
Ok(l) => Some(serde_json::from_str(l.as_str()).map_err(|e| e.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FrontendRequestWriter {
|
||||
pub fn request(&mut self, request: FrontendRequest) -> Result<(), io::Error> {
|
||||
let mut json = serde_json::to_string(&request).unwrap();
|
||||
log::debug!("requesting: {json}");
|
||||
json.push('\n');
|
||||
self.line_writer.write_all(json.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn connect() -> Result<(FrontendEventReader, FrontendRequestWriter), ConnectionError> {
|
||||
let rx = wait_for_service()?;
|
||||
let tx = rx.try_clone()?;
|
||||
let buf_reader = BufReader::new(rx);
|
||||
let lines = buf_reader.lines();
|
||||
let line_writer = LineWriter::new(tx);
|
||||
let reader = FrontendEventReader { lines };
|
||||
let writer = FrontendRequestWriter { line_writer };
|
||||
Ok((reader, writer))
|
||||
}
|
||||
|
||||
/// wait for the lan-mouse socket to come online
|
||||
#[cfg(unix)]
|
||||
fn wait_for_service() -> Result<UnixStream, ConnectionError> {
|
||||
let socket_path = crate::default_socket_path()?;
|
||||
let mut duration = Duration::from_millis(10);
|
||||
loop {
|
||||
if let Ok(stream) = UnixStream::connect(&socket_path) {
|
||||
break Ok(stream);
|
||||
}
|
||||
// a signaling mechanism or inotify could be used to
|
||||
// improve this
|
||||
thread::sleep(exponential_back_off(&mut duration));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn wait_for_service() -> Result<TcpStream, ConnectionError> {
|
||||
let mut duration = Duration::from_millis(10);
|
||||
loop {
|
||||
if let Ok(stream) = TcpStream::connect("127.0.0.1:5252") {
|
||||
break Ok(stream);
|
||||
}
|
||||
thread::sleep(exponential_back_off(&mut duration));
|
||||
}
|
||||
}
|
||||
|
||||
fn exponential_back_off(duration: &mut Duration) -> Duration {
|
||||
let new = duration.saturating_mul(2);
|
||||
*duration = min(new, Duration::from_secs(1));
|
||||
*duration
|
||||
}
|
||||
104
lan-mouse-ipc/src/connect_async.rs
Normal file
104
lan-mouse-ipc/src/connect_async.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use crate::{ConnectionError, FrontendEvent, FrontendRequest, IpcError};
|
||||
use std::{
|
||||
cmp::min,
|
||||
io,
|
||||
task::{ready, Poll},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use futures::{Stream, StreamExt};
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, ReadHalf, WriteHalf};
|
||||
use tokio_stream::wrappers::LinesStream;
|
||||
|
||||
#[cfg(unix)]
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
#[cfg(windows)]
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
pub struct AsyncFrontendEventReader {
|
||||
#[cfg(unix)]
|
||||
lines_stream: LinesStream<BufReader<ReadHalf<UnixStream>>>,
|
||||
#[cfg(windows)]
|
||||
lines_stream: LinesStream<BufReader<ReadHalf<TcpStream>>>,
|
||||
}
|
||||
|
||||
pub struct AsyncFrontendRequestWriter {
|
||||
#[cfg(unix)]
|
||||
tx: WriteHalf<UnixStream>,
|
||||
#[cfg(windows)]
|
||||
tx: WriteHalf<TcpStream>,
|
||||
}
|
||||
|
||||
impl Stream for AsyncFrontendEventReader {
|
||||
type Item = Result<FrontendEvent, IpcError>;
|
||||
|
||||
fn poll_next(
|
||||
mut self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
let line = ready!(self.lines_stream.poll_next_unpin(cx));
|
||||
let event = line.map(|l| {
|
||||
l.map_err(Into::<IpcError>::into)
|
||||
.and_then(|l| serde_json::from_str(l.as_str()).map_err(|e| e.into()))
|
||||
});
|
||||
Poll::Ready(event)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncFrontendRequestWriter {
|
||||
pub async fn request(&mut self, request: FrontendRequest) -> Result<(), io::Error> {
|
||||
let mut json = serde_json::to_string(&request).unwrap();
|
||||
log::debug!("requesting: {json}");
|
||||
json.push('\n');
|
||||
self.tx.write_all(json.as_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn connect_async(
|
||||
) -> Result<(AsyncFrontendEventReader, AsyncFrontendRequestWriter), ConnectionError> {
|
||||
let stream = wait_for_service().await?;
|
||||
#[cfg(unix)]
|
||||
let (rx, tx): (ReadHalf<UnixStream>, WriteHalf<UnixStream>) = tokio::io::split(stream);
|
||||
#[cfg(windows)]
|
||||
let (rx, tx): (ReadHalf<TcpStream>, WriteHalf<TcpStream>) = tokio::io::split(stream);
|
||||
let buf_reader = BufReader::new(rx);
|
||||
let lines = buf_reader.lines();
|
||||
let lines_stream = LinesStream::new(lines);
|
||||
let reader = AsyncFrontendEventReader { lines_stream };
|
||||
let writer = AsyncFrontendRequestWriter { tx };
|
||||
Ok((reader, writer))
|
||||
}
|
||||
|
||||
/// wait for the lan-mouse socket to come online
|
||||
#[cfg(unix)]
|
||||
async fn wait_for_service() -> Result<UnixStream, ConnectionError> {
|
||||
let socket_path = crate::default_socket_path()?;
|
||||
let mut duration = Duration::from_millis(10);
|
||||
loop {
|
||||
if let Ok(stream) = UnixStream::connect(&socket_path).await {
|
||||
break Ok(stream);
|
||||
}
|
||||
// a signaling mechanism or inotify could be used to
|
||||
// improve this
|
||||
tokio::time::sleep(exponential_back_off(&mut duration)).await;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
async fn wait_for_service() -> Result<TcpStream, ConnectionError> {
|
||||
let mut duration = Duration::from_millis(10);
|
||||
loop {
|
||||
if let Ok(stream) = TcpStream::connect("127.0.0.1:5252").await {
|
||||
break Ok(stream);
|
||||
}
|
||||
tokio::time::sleep(exponential_back_off(&mut duration)).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn exponential_back_off(duration: &mut Duration) -> Duration {
|
||||
let new = duration.saturating_mul(2);
|
||||
*duration = min(new, Duration::from_secs(1));
|
||||
*duration
|
||||
}
|
||||
262
lan-mouse-ipc/src/lib.rs
Normal file
262
lan-mouse-ipc/src/lib.rs
Normal file
@@ -0,0 +1,262 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
env::VarError,
|
||||
fmt::Display,
|
||||
io,
|
||||
net::{IpAddr, SocketAddr},
|
||||
str::FromStr,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::{
|
||||
env,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod connect;
|
||||
mod connect_async;
|
||||
mod listen;
|
||||
|
||||
pub use connect::{connect, FrontendEventReader, FrontendRequestWriter};
|
||||
pub use connect_async::{connect_async, AsyncFrontendEventReader, AsyncFrontendRequestWriter};
|
||||
pub use listen::AsyncFrontendListener;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConnectionError {
|
||||
#[error(transparent)]
|
||||
SocketPath(#[from] SocketPathError),
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ListenerCreationError {
|
||||
#[error("could not determine socket-path: `{0}`")]
|
||||
SocketPath(#[from] SocketPathError),
|
||||
#[error("service already running!")]
|
||||
AlreadyRunning,
|
||||
#[error("failed to bind lan-mouse socket: `{0}`")]
|
||||
Bind(io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IpcError {
|
||||
#[error("io error occured: `{0}`")]
|
||||
Io(#[from] io::Error),
|
||||
#[error("invalid json: `{0}`")]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
Connection(#[from] ConnectionError),
|
||||
#[error(transparent)]
|
||||
Listen(#[from] ListenerCreationError),
|
||||
}
|
||||
|
||||
pub const DEFAULT_PORT: u16 = 4242;
|
||||
|
||||
#[derive(Debug, Default, Eq, Hash, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum Position {
|
||||
#[default]
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("not a valid position: {pos}")]
|
||||
pub struct PositionParseError {
|
||||
pos: String,
|
||||
}
|
||||
|
||||
impl FromStr for Position {
|
||||
type Err = PositionParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"left" => Ok(Self::Left),
|
||||
"right" => Ok(Self::Right),
|
||||
"top" => Ok(Self::Top),
|
||||
"bottom" => Ok(Self::Bottom),
|
||||
_ => Err(PositionParseError { pos: s.into() }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Position {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Position::Left => "left",
|
||||
Position::Right => "right",
|
||||
Position::Top => "top",
|
||||
Position::Bottom => "bottom",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Position {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
match s {
|
||||
"left" => Ok(Position::Left),
|
||||
"right" => Ok(Position::Right),
|
||||
"top" => Ok(Position::Top),
|
||||
"bottom" => Ok(Position::Bottom),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct ClientConfig {
|
||||
/// hostname of this client
|
||||
pub hostname: Option<String>,
|
||||
/// fix ips, determined by the user
|
||||
pub fix_ips: Vec<IpAddr>,
|
||||
/// both active_addr and addrs can be None / empty so port needs to be stored seperately
|
||||
pub port: u16,
|
||||
/// position of a client on screen
|
||||
pub pos: Position,
|
||||
/// enter hook
|
||||
pub cmd: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for ClientConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
port: DEFAULT_PORT,
|
||||
hostname: Default::default(),
|
||||
fix_ips: Default::default(),
|
||||
pos: Default::default(),
|
||||
cmd: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type ClientHandle = u64;
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct ClientState {
|
||||
/// events should be sent to and received from the client
|
||||
pub active: bool,
|
||||
/// `active` address of the client, used to send data to.
|
||||
/// This should generally be the socket address where data
|
||||
/// was last received from.
|
||||
pub active_addr: Option<SocketAddr>,
|
||||
/// tracks whether or not the client is responding to pings
|
||||
pub alive: bool,
|
||||
/// ips from dns
|
||||
pub dns_ips: Vec<IpAddr>,
|
||||
/// all ip addresses associated with a particular client
|
||||
/// e.g. Laptops usually have at least an ethernet and a wifi port
|
||||
/// which have different ip addresses
|
||||
pub ips: HashSet<IpAddr>,
|
||||
/// client has pressed keys
|
||||
pub has_pressed_keys: bool,
|
||||
/// dns resolving in progress
|
||||
pub resolving: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum FrontendEvent {
|
||||
/// a client was created
|
||||
Created(ClientHandle, ClientConfig, ClientState),
|
||||
/// no such client
|
||||
NoSuchClient(ClientHandle),
|
||||
/// state changed
|
||||
State(ClientHandle, ClientConfig, ClientState),
|
||||
/// the client was deleted
|
||||
Deleted(ClientHandle),
|
||||
/// new port, reason of failure (if failed)
|
||||
PortChanged(u16, Option<String>),
|
||||
/// list of all clients, used for initial state synchronization
|
||||
Enumerate(Vec<(ClientHandle, ClientConfig, ClientState)>),
|
||||
/// an error occured
|
||||
Error(String),
|
||||
/// capture status
|
||||
CaptureStatus(Status),
|
||||
/// emulation status
|
||||
EmulationStatus(Status),
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub enum FrontendRequest {
|
||||
/// activate/deactivate client
|
||||
Activate(ClientHandle, bool),
|
||||
/// add a new client
|
||||
Create,
|
||||
/// change the listen port (recreate udp listener)
|
||||
ChangePort(u16),
|
||||
/// remove a client
|
||||
Delete(ClientHandle),
|
||||
/// request an enumeration of all clients
|
||||
Enumerate(),
|
||||
/// resolve dns
|
||||
ResolveDns(ClientHandle),
|
||||
/// update hostname
|
||||
UpdateHostname(ClientHandle, Option<String>),
|
||||
/// update port
|
||||
UpdatePort(ClientHandle, u16),
|
||||
/// update position
|
||||
UpdatePosition(ClientHandle, Position),
|
||||
/// update fix-ips
|
||||
UpdateFixIps(ClientHandle, Vec<IpAddr>),
|
||||
/// request the state of the given client
|
||||
GetState(ClientHandle),
|
||||
/// request reenabling input capture
|
||||
EnableCapture,
|
||||
/// request reenabling input emulation
|
||||
EnableEmulation,
|
||||
/// synchronize all state
|
||||
Sync,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub enum Status {
|
||||
#[default]
|
||||
Disabled,
|
||||
Enabled,
|
||||
}
|
||||
|
||||
impl From<Status> for bool {
|
||||
fn from(status: Status) -> Self {
|
||||
match status {
|
||||
Status::Enabled => true,
|
||||
Status::Disabled => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
const LAN_MOUSE_SOCKET_NAME: &str = "lan-mouse-socket.sock";
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SocketPathError {
|
||||
#[error("could not determine $XDG_RUNTIME_DIR: `{0}`")]
|
||||
XdgRuntimeDirNotFound(VarError),
|
||||
#[error("could not determine $HOME: `{0}`")]
|
||||
HomeDirNotFound(VarError),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
pub fn default_socket_path() -> Result<PathBuf, SocketPathError> {
|
||||
let xdg_runtime_dir =
|
||||
env::var("XDG_RUNTIME_DIR").map_err(SocketPathError::XdgRuntimeDirNotFound)?;
|
||||
Ok(Path::new(xdg_runtime_dir.as_str()).join(LAN_MOUSE_SOCKET_NAME))
|
||||
}
|
||||
|
||||
#[cfg(all(unix, target_os = "macos"))]
|
||||
pub fn default_socket_path() -> Result<PathBuf, SocketPathError> {
|
||||
let home = env::var("HOME").map_err(SocketPathError::HomeDirNotFound)?;
|
||||
Ok(Path::new(home.as_str())
|
||||
.join("Library")
|
||||
.join("Caches")
|
||||
.join(LAN_MOUSE_SOCKET_NAME))
|
||||
}
|
||||
147
lan-mouse-ipc/src/listen.rs
Normal file
147
lan-mouse-ipc/src/listen.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use futures::{stream::SelectAll, Stream, StreamExt};
|
||||
#[cfg(unix)]
|
||||
use std::path::PathBuf;
|
||||
use std::{
|
||||
io::ErrorKind,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, ReadHalf, WriteHalf};
|
||||
use tokio_stream::wrappers::LinesStream;
|
||||
|
||||
#[cfg(unix)]
|
||||
use tokio::net::UnixListener;
|
||||
#[cfg(unix)]
|
||||
use tokio::net::UnixStream;
|
||||
|
||||
#[cfg(windows)]
|
||||
use tokio::net::TcpListener;
|
||||
#[cfg(windows)]
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::{FrontendEvent, FrontendRequest, IpcError, ListenerCreationError};
|
||||
|
||||
pub struct AsyncFrontendListener {
|
||||
#[cfg(windows)]
|
||||
listener: TcpListener,
|
||||
#[cfg(unix)]
|
||||
listener: UnixListener,
|
||||
#[cfg(unix)]
|
||||
socket_path: PathBuf,
|
||||
#[cfg(unix)]
|
||||
line_streams: SelectAll<LinesStream<BufReader<ReadHalf<UnixStream>>>>,
|
||||
#[cfg(windows)]
|
||||
line_streams: SelectAll<LinesStream<BufReader<ReadHalf<TcpStream>>>>,
|
||||
#[cfg(unix)]
|
||||
tx_streams: Vec<WriteHalf<UnixStream>>,
|
||||
#[cfg(windows)]
|
||||
tx_streams: Vec<WriteHalf<TcpStream>>,
|
||||
}
|
||||
|
||||
impl AsyncFrontendListener {
|
||||
pub async fn new() -> Result<Self, ListenerCreationError> {
|
||||
#[cfg(unix)]
|
||||
let (socket_path, listener) = {
|
||||
let socket_path = crate::default_socket_path()?;
|
||||
|
||||
log::debug!("remove socket: {:?}", socket_path);
|
||||
if socket_path.exists() {
|
||||
// try to connect to see if some other instance
|
||||
// of lan-mouse is already running
|
||||
match UnixStream::connect(&socket_path).await {
|
||||
// connected -> lan-mouse is already running
|
||||
Ok(_) => return Err(ListenerCreationError::AlreadyRunning),
|
||||
// lan-mouse is not running but a socket was left behind
|
||||
Err(e) => {
|
||||
log::debug!("{socket_path:?}: {e} - removing left behind socket");
|
||||
let _ = std::fs::remove_file(&socket_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
let listener = match UnixListener::bind(&socket_path) {
|
||||
Ok(ls) => ls,
|
||||
// some other lan-mouse instance has bound the socket in the meantime
|
||||
Err(e) if e.kind() == ErrorKind::AddrInUse => {
|
||||
return Err(ListenerCreationError::AlreadyRunning)
|
||||
}
|
||||
Err(e) => return Err(ListenerCreationError::Bind(e)),
|
||||
};
|
||||
(socket_path, listener)
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let listener = match TcpListener::bind("127.0.0.1:5252").await {
|
||||
Ok(ls) => ls,
|
||||
// some other lan-mouse instance has bound the socket in the meantime
|
||||
Err(e) if e.kind() == ErrorKind::AddrInUse => {
|
||||
return Err(ListenerCreationError::AlreadyRunning)
|
||||
}
|
||||
Err(e) => return Err(ListenerCreationError::Bind(e)),
|
||||
};
|
||||
|
||||
let adapter = Self {
|
||||
listener,
|
||||
#[cfg(unix)]
|
||||
socket_path,
|
||||
line_streams: SelectAll::new(),
|
||||
tx_streams: vec![],
|
||||
};
|
||||
|
||||
Ok(adapter)
|
||||
}
|
||||
|
||||
pub async fn broadcast(&mut self, notify: FrontendEvent) {
|
||||
// encode event
|
||||
let mut json = serde_json::to_string(¬ify).unwrap();
|
||||
json.push('\n');
|
||||
|
||||
let mut keep = vec![];
|
||||
// TODO do simultaneously
|
||||
for tx in self.tx_streams.iter_mut() {
|
||||
// write len + payload
|
||||
if tx.write(json.as_bytes()).await.is_err() {
|
||||
keep.push(false);
|
||||
continue;
|
||||
}
|
||||
keep.push(true);
|
||||
}
|
||||
|
||||
// could not find a better solution because async
|
||||
let mut keep = keep.into_iter();
|
||||
self.tx_streams.retain(|_| keep.next().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl Drop for AsyncFrontendListener {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("remove socket: {:?}", self.socket_path);
|
||||
let _ = std::fs::remove_file(&self.socket_path);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for AsyncFrontendListener {
|
||||
type Item = Result<FrontendRequest, IpcError>;
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
if let Poll::Ready(Some(Ok(l))) = self.line_streams.poll_next_unpin(cx) {
|
||||
let request = serde_json::from_str(l.as_str()).map_err(|e| e.into());
|
||||
return Poll::Ready(Some(request));
|
||||
}
|
||||
let mut sync = false;
|
||||
while let Poll::Ready(Ok((stream, _))) = self.listener.poll_accept(cx) {
|
||||
let (rx, tx) = tokio::io::split(stream);
|
||||
let buf_reader = BufReader::new(rx);
|
||||
let lines = buf_reader.lines();
|
||||
let lines = LinesStream::new(lines);
|
||||
self.line_streams.push(lines);
|
||||
self.tx_streams.push(tx);
|
||||
sync = true;
|
||||
}
|
||||
if sync {
|
||||
Poll::Ready(Some(Ok(FrontendRequest::Sync)))
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user