First unix ipc implementation
This commit is contained in:
		
							parent
							
								
									cfadebc733
								
							
						
					
					
						commit
						ee611c3a03
					
				
							
								
								
									
										90
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										90
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -87,12 +87,77 @@ dependencies = [
 | 
			
		|||
 "winapi",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "const_fn"
 | 
			
		||||
version = "0.4.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "constant_time_eq"
 | 
			
		||||
version = "0.1.5"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam"
 | 
			
		||||
version = "0.8.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "crossbeam-channel",
 | 
			
		||||
 "crossbeam-deque",
 | 
			
		||||
 "crossbeam-epoch",
 | 
			
		||||
 "crossbeam-queue",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-channel"
 | 
			
		||||
version = "0.5.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-deque"
 | 
			
		||||
version = "0.8.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "crossbeam-epoch",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-epoch"
 | 
			
		||||
version = "0.9.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "const_fn",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "memoffset",
 | 
			
		||||
 "scopeguard",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-queue"
 | 
			
		||||
version = "0.3.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "cfg-if",
 | 
			
		||||
 "crossbeam-utils",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "crossbeam-utils"
 | 
			
		||||
version = "0.8.1"
 | 
			
		||||
| 
						 | 
				
			
			@ -188,6 +253,18 @@ dependencies = [
 | 
			
		|||
 "widestring",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "espanso-ipc"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "crossbeam",
 | 
			
		||||
 "log",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "thiserror",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "espanso-ui"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -307,6 +384,15 @@ version = "2.3.4"
 | 
			
		|||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "memoffset"
 | 
			
		||||
version = "0.6.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "autocfg",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "notify-rust"
 | 
			
		||||
version = "4.2.2"
 | 
			
		||||
| 
						 | 
				
			
			@ -483,9 +569,9 @@ dependencies = [
 | 
			
		|||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "serde_json"
 | 
			
		||||
version = "1.0.61"
 | 
			
		||||
version = "1.0.62"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
 | 
			
		||||
checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "itoa",
 | 
			
		||||
 "ryu",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,4 +5,5 @@ members = [
 | 
			
		|||
  "espanso-detect",
 | 
			
		||||
  "espanso-ui",
 | 
			
		||||
  "espanso-inject",
 | 
			
		||||
  "espanso-ipc",
 | 
			
		||||
]
 | 
			
		||||
							
								
								
									
										17
									
								
								espanso-ipc/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								espanso-ipc/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "espanso-ipc"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
authors = ["Federico Terzi <federico-terzi@users.noreply.github.com>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
log = "0.4.14"
 | 
			
		||||
anyhow = "1.0.38"
 | 
			
		||||
thiserror = "1.0.23"
 | 
			
		||||
serde = { version = "1.0.123", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0.62"
 | 
			
		||||
crossbeam = "0.8.0"
 | 
			
		||||
 | 
			
		||||
[target.'cfg(windows)'.dependencies]
 | 
			
		||||
 | 
			
		||||
[target.'cfg(unix)'.dependencies]
 | 
			
		||||
							
								
								
									
										95
									
								
								espanso-ipc/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								espanso-ipc/src/lib.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,95 @@
 | 
			
		|||
/*
 | 
			
		||||
 * This file is part of espanso.
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2019-2021 Federico Terzi
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use serde::{Serialize, de::DeserializeOwned};
 | 
			
		||||
use thiserror::Error;
 | 
			
		||||
use crossbeam::channel::{Receiver, unbounded};
 | 
			
		||||
 | 
			
		||||
#[cfg(target_os = "windows")]
 | 
			
		||||
pub mod windows;
 | 
			
		||||
 | 
			
		||||
#[cfg(not(target_os = "windows"))]
 | 
			
		||||
pub mod unix;
 | 
			
		||||
 | 
			
		||||
pub trait IPCServer<Event> {
 | 
			
		||||
  fn run(&self) -> Result<()>;
 | 
			
		||||
  fn accept_one(&self) -> Result<()>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait IPCClient<Event> {
 | 
			
		||||
  fn send(&self, event: Event) -> Result<()>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(not(target_os = "windows"))]
 | 
			
		||||
pub fn server<Event: Send + Sync + DeserializeOwned>(id: &str, parent_dir: &Path) -> Result<(impl IPCServer<Event>, Receiver<Event>)> {
 | 
			
		||||
  let (sender, receiver) = unbounded();
 | 
			
		||||
  let server = unix::UnixIPCServer::new(id, parent_dir, sender)?;
 | 
			
		||||
  Ok((server, receiver))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(not(target_os = "windows"))]
 | 
			
		||||
pub fn client<Event: Serialize>(id: &str, parent_dir: &Path) -> Result<impl IPCClient<Event>> {
 | 
			
		||||
  let client = unix::UnixIPCClient::new(id, parent_dir)?;
 | 
			
		||||
  Ok(client)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Error, Debug)]
 | 
			
		||||
pub enum IPCServerError {
 | 
			
		||||
  #[error("stream ended")]
 | 
			
		||||
  StreamEnded(#[from] std::io::Error),
 | 
			
		||||
 | 
			
		||||
  #[error("send failed")]
 | 
			
		||||
  SendFailed(),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
  use super::*;
 | 
			
		||||
  use serde::{Serialize, Deserialize};
 | 
			
		||||
 | 
			
		||||
  #[derive(Serialize, Deserialize)]
 | 
			
		||||
  enum Event {
 | 
			
		||||
    Bar,
 | 
			
		||||
    Foo(String),
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn ipc_works_correctly() {
 | 
			
		||||
    let (server, receiver) = server::<Event>("testespansoipc", &std::env::temp_dir()).unwrap();
 | 
			
		||||
    let server_handle = std::thread::spawn(move || {
 | 
			
		||||
      server.accept_one().unwrap();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let client = client::<Event>("testespansoipc", &std::env::temp_dir()).unwrap();
 | 
			
		||||
    client.send(Event::Foo("hello".to_string())).unwrap();
 | 
			
		||||
    
 | 
			
		||||
    let event = receiver.recv().unwrap();
 | 
			
		||||
    assert!(matches!(event, Event::Foo(x) if x == "hello"));
 | 
			
		||||
 | 
			
		||||
    server_handle.join().unwrap();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  #[test]
 | 
			
		||||
  fn ipc_client_fails_to_send() {
 | 
			
		||||
    let client = client::<Event>("testespansoipc", &std::env::temp_dir()).unwrap();
 | 
			
		||||
    assert!(client.send(Event::Foo("hello".to_string())).is_err());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										122
									
								
								espanso-ipc/src/unix.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								espanso-ipc/src/unix.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,122 @@
 | 
			
		|||
/*
 | 
			
		||||
 * This file is part of espanso.
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2019-2021 Federico Terzi
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use crossbeam::channel::Sender;
 | 
			
		||||
use log::{error, info};
 | 
			
		||||
use serde::{de::DeserializeOwned, Serialize};
 | 
			
		||||
use std::{
 | 
			
		||||
  io::{BufReader, Read, Write},
 | 
			
		||||
  os::unix::net::{UnixListener, UnixStream},
 | 
			
		||||
  path::{Path, PathBuf},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{IPCClient, IPCServer, IPCServerError};
 | 
			
		||||
 | 
			
		||||
pub struct UnixIPCServer<Event> {
 | 
			
		||||
  listener: UnixListener,
 | 
			
		||||
  sender: Sender<Event>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<Event> UnixIPCServer<Event> {
 | 
			
		||||
  pub fn new(id: &str, parent_dir: &Path, sender: Sender<Event>) -> Result<Self> {
 | 
			
		||||
    let socket_path = parent_dir.join(format!("{}.sock", id));
 | 
			
		||||
 | 
			
		||||
    // Remove previous Unix socket
 | 
			
		||||
    if socket_path.exists() {
 | 
			
		||||
      std::fs::remove_file(&socket_path)?;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let listener = UnixListener::bind(&socket_path)?;
 | 
			
		||||
 | 
			
		||||
    info!(
 | 
			
		||||
      "binded to IPC unix socket: {}",
 | 
			
		||||
      socket_path.to_string_lossy()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    Ok(Self { listener, sender })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<Event: Send + Sync + DeserializeOwned> IPCServer<Event> for UnixIPCServer<Event> {
 | 
			
		||||
  fn run(&self) -> anyhow::Result<()> {
 | 
			
		||||
    loop {
 | 
			
		||||
      self.accept_one()?;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fn accept_one(&self) -> Result<()> {
 | 
			
		||||
    let connection = self.listener.accept();
 | 
			
		||||
 | 
			
		||||
    match connection {
 | 
			
		||||
      Ok((stream, _)) => {
 | 
			
		||||
        let mut json_str = String::new();
 | 
			
		||||
        let mut buf_reader = BufReader::new(stream);
 | 
			
		||||
        let result = buf_reader.read_to_string(&mut json_str);
 | 
			
		||||
 | 
			
		||||
        match result {
 | 
			
		||||
          Ok(_) => {
 | 
			
		||||
            let event: Result<Event, serde_json::Error> = serde_json::from_str(&json_str);
 | 
			
		||||
            match event {
 | 
			
		||||
              Ok(event) => {
 | 
			
		||||
                if self.sender.send(event).is_err() {
 | 
			
		||||
                  return Err(IPCServerError::SendFailed().into());
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              Err(error) => {
 | 
			
		||||
                error!("received malformed event from ipc stream: {}", error);
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          Err(error) => {
 | 
			
		||||
            error!("error reading ipc stream: {}", error);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      Err(err) => {
 | 
			
		||||
        return Err(IPCServerError::StreamEnded(err).into());
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct UnixIPCClient {
 | 
			
		||||
  socket_path: PathBuf,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl UnixIPCClient {
 | 
			
		||||
  pub fn new(id: &str, parent_dir: &Path) -> Result<Self> {
 | 
			
		||||
    let socket_path = parent_dir.join(format!("{}.sock", id));
 | 
			
		||||
 | 
			
		||||
    Ok(Self { socket_path })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<Event: Serialize> IPCClient<Event> for UnixIPCClient {
 | 
			
		||||
  fn send(&self, event: Event) -> Result<()> {
 | 
			
		||||
    let mut stream = UnixStream::connect(&self.socket_path)?;
 | 
			
		||||
 | 
			
		||||
    let json_event = serde_json::to_string(&event)?;
 | 
			
		||||
    stream.write_all(json_event.as_bytes())?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								espanso-ipc/src/windows.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								espanso-ipc/src/windows.rs
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
/*
 | 
			
		||||
 * This file is part of espanso.
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2019-2021 Federico Terzi
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is free software: you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * espanso is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with espanso.  If not, see <https://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user