summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2020-07-20 20:10:56 +0200
committerCanop <cano.petrole@gmail.com>2020-07-20 20:10:56 +0200
commit84d857e7fa6e8749c21b3c937c96cce9c25356e9 (patch)
tree878688bea2b4c54fbcd5b996cf5d098194e0a399
parentf0cd893b028cec09bd7a35b3f4d00c64c31cea0f (diff)
"client-server" feature
-rw-r--r--CHANGELOG.md1
-rw-r--r--Cargo.toml4
-rw-r--r--client-server.md58
-rw-r--r--src/app/app.rs102
-rw-r--r--src/clap.rs19
-rw-r--r--src/cli.rs34
-rw-r--r--src/command/mod.rs2
-rw-r--r--src/command/sequence.rs135
-rw-r--r--src/errors.rs10
-rw-r--r--src/lib.rs4
-rw-r--r--src/net/client.rs32
-rw-r--r--src/net/message.rs64
-rw-r--r--src/net/mod.rs15
-rw-r--r--src/net/server.rs75
-rw-r--r--src/task_sync.rs29
15 files changed, 484 insertions, 100 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cfa89b1..0b4a95e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,6 @@
### next version
- preview's pattern is kept when changing file
+- "client-server" feature (see client-server.md)
<a name="v0.19.1"></a>
### v0.19.1 - 2020-07-17
diff --git a/Cargo.toml b/Cargo.toml
index 5a13e37..8ec7bef 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,6 +13,10 @@ readme = "README.md"
build = "build.rs"
exclude = ["website", "broot*.zip"]
+[features]
+default = []
+client-server = []
+
[dependencies]
bet = "0.3.4"
clap = { version="2.33", default-features=false, features=["suggestions"] }
diff --git a/client-server.md b/client-server.md
new file mode 100644
index 0000000..9734b62
--- /dev/null
+++ b/client-server.md
@@ -0,0 +1,58 @@
+
+# Presentation
+
+`"client-server"` is a feature declared in Cargo.toml.
+
+It's not enabled by default.
+
+If you want to compile with the feature, do
+
+ cargo build --release --features "client-server"
+
+To run in debug mode with the feature, do
+
+ cargo run --features "client-server" --
+
+This feature add 2 launch arguments that you can see with
+
+ cargo run --features "client-server" -- --help
+
+Those arguments are:
+
+* `--listen <instance_name>` : listen on a specific socket
+* `--send <instance_name>`: send the command(s) to the given server and quit
+
+For example if you start broot with
+
+ br --listen my_broot
+
+broot will run normally but will *also* listen to commands sent from elsewhere (using linux sockets).
+
+If you want to do the same in debug mode, it's
+
+ cargo run --features "client-server" -- --listen my_broot
+
+Now that the "server" is running, try
+
+ br --send my_broot -c "img;:parent;:focus"
+
+this will make the running "server" search for something like "img" and focus its parent.
+
+If you don't pass the `--cmd` (shortened in `-c`) argument, then the server is told to focus the current directory or the path given as argument.
+
+# Development
+
+This feature started here: https://github.com/Canop/broot/issues/225
+
+and is being discussed and developped between @Canop (@dystroy on Miaou) and @SRGOM (@fiAtcBr on Miaou) and you're welcome to contribute on [Miaou](https://miaou.dystroy.org/3490).
+
+# TODO:
+
+- [ ] merge in master ?
+- [ ] have convincing use cases described
+- [ ] implement a command "GetRoot" to
+- [ ] have convincing use cases implemented
+- [ ] make available with TCP localhost sockets on windows ?
+- [ ] stop hiding behing a compilation flag
+
+
diff --git a/src/app/app.rs b/src/app/app.rs
index 4232414..5d1187c 100644
--- a/src/app/app.rs
+++ b/src/app/app.rs
@@ -2,16 +2,17 @@ use {
super::*,
crate::{
browser::BrowserState,
- command::{parse_command_sequence, Command},
+ command::{Command, Sequence},
conf::Conf,
display::{Areas, Screen, W},
errors::ProgramError,
file_sum, git,
launchable::Launchable,
skin::*,
- task_sync::Dam,
+ task_sync::{Dam, Either},
verb::Internal,
},
+ crossbeam::channel::unbounded,
crossterm::event::KeyModifiers,
std::{
io::Write,
@@ -379,23 +380,19 @@ impl App {
screen.clear_bottom_right_char(w, &skin.focused)?;
- // if some commands were passed to the application
- // we execute them before even starting listening for events
- if let Some(unparsed_commands) = &con.launch_args.commands {
- for (input, arg_cmd) in parse_command_sequence(unparsed_commands, con)? {
- self.mut_panel().set_input_content(input);
- self.apply_command(w, arg_cmd, screen, &skin.focused, con)?;
- self.display_panels(w, screen, &skin, con)?;
- w.flush()?;
- self.do_pending_tasks(screen, con, &mut dam)?;
- self.display_panels(w, screen, &skin, con)?;
- w.flush()?;
- if self.quitting {
- return Ok(self.launch_at_end.take());
- }
- }
+ // we create a channel for unparsed raw sequence which may come
+ // from the --cmd argument or from the server module
+ let (tx_seqs, rx_seqs) = unbounded::<Sequence>();
+
+ if let Some(raw_sequence) = &con.launch_args.commands {
+ tx_seqs.send(Sequence::new_local(raw_sequence.to_string())).unwrap();
}
+ #[cfg(feature="client-server")]
+ let _server = con.launch_args.listen.as_ref()
+ .map(|server_name| crate::net::Server::new(&server_name, tx_seqs.clone()))
+ .transpose()?;
+
loop {
if !self.quitting {
self.display_panels(w, screen, &skin, con)?;
@@ -405,39 +402,60 @@ impl App {
w.flush()?;
}
}
- let event = match dam.next_event() {
- Some(event) => event,
- None => {
+
+ match dam.next(&rx_seqs) {
+ Either::First(Some(event)) => {
+ debug!("event: {:?}", &event);
+ match event {
+ Event::Click(x, y, KeyModifiers::NONE)
+ if self.clicked_panel_index(x, y, screen) != self.active_panel_idx =>
+ {
+ // panel activation click
+ // this will be cleaner when if let will be allowed in match guards with
+ // chaining (currently experimental)
+ self.active_panel_idx = self.clicked_panel_index(x, y, screen);
+ }
+ Event::Resize(w, h) => {
+ screen.set_terminal_size(w, h, con);
+ Areas::resize_all(self.panels.as_mut_slice(), screen, self.preview.is_some())?;
+ for panel in &mut self.panels {
+ panel.mut_state().refresh(screen, con);
+ }
+ }
+ _ => {
+ // event handled by the panel
+ let cmd = self.mut_panel().add_event(w, event, con)?;
+ debug!("command after add_event: {:?}", &cmd);
+ self.apply_command(w, cmd, screen, &skin.focused, con)?;
+ }
+ }
+ event_source.unblock(self.quitting);
+ }
+ Either::First(None) => {
// this is how we quit the application,
// when the input thread is properly closed
break;
}
- };
- debug!("event: {:?}", &event);
- match event {
- Event::Click(x, y, KeyModifiers::NONE)
- if self.clicked_panel_index(x, y, screen) != self.active_panel_idx =>
- {
- // panel activation clic
- // this will be cleaner when if let will be allowed in match guards with
- // chaining (currently experimental)
- self.active_panel_idx = self.clicked_panel_index(x, y, screen);
- }
- Event::Resize(w, h) => {
- screen.set_terminal_size(w, h, con);
- Areas::resize_all(self.panels.as_mut_slice(), screen, self.preview.is_some())?;
- for panel in &mut self.panels {
- panel.mut_state().refresh(screen, con);
+ Either::Second(Some(raw_sequence)) => {
+ debug!("got sequence: {:?}", &raw_sequence);
+ for (input, arg_cmd) in raw_sequence.parse(con)? {
+ self.mut_panel().set_input_content(&input);
+ self.apply_command(w, arg_cmd, screen, &skin.focused, con)?;
+ self.display_panels(w, screen, &skin, con)?;
+ w.flush()?;
+ self.do_pending_tasks(screen, con, &mut dam)?;
+ self.display_panels(w, screen, &skin, con)?;
+ w.flush()?;
+ if self.quitting {
+ // is that a 100% safe way of quitting ?
+ return Ok(self.launch_at_end.take());
+ }
}
}
- _ => {
- // event handled by the panel
- let cmd = self.mut_panel().add_event(w, event, con)?;
- debug!("command after add_event: {:?}", &cmd);
- self.apply_command(w, cmd, screen, &skin.focused, con)?;
+ Either::Second(None) => {
+ warn!("I didn't expect a None to occur here");
}
}
- event_source.unblock(self.quitting);
}
Ok(self.launch_at_end.take())
diff --git a/src/clap.rs b/src/clap.rs
index 94dea93..f1e2f69 100644
--- a/src/clap.rs
+++ b/src/clap.rs
@@ -3,7 +3,7 @@
/// declare the possible CLI arguments
pub fn clap_app() -> clap::App<'static, 'static> {
- clap::App::new("broot")
+ let app = clap::App::new("broot")
.version(env!("CARGO_PKG_VERSION"))
.author("dystroy <denys.seguret@gmail.com>")
.about("A tree explorer and a customizable launcher")
@@ -194,5 +194,20 @@ pub fn clap_app() -> clap::App<'static, 'static> {
.value_name("shell")
.help("Print to stdout the br function for a given shell"),
)
- .setting(clap::AppSettings::DeriveDisplayOrder)
+ .setting(clap::AppSettings::DeriveDisplayOrder);
+ #[cfg(feature="client-server")]
+ let app = app
+ .arg(
+ clap::Arg::with_name("listen")
+ .long("listen")
+ .takes_value(true)
+ .help("Listen for commands")
+ )
+ .arg(
+ clap::Arg::with_name("send")
+ .long("send")
+ .takes_value(true)
+ .help("send command and quits")
+ );
+ app
}
diff --git a/src/cli.rs b/src/cli.rs
index 99cc0e3..a0ab2bd 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -26,6 +26,7 @@ use {
},
};
+
/// launch arguments related to installation
/// (not used by the application after the first step)
struct InstallLaunchArgs {
@@ -56,11 +57,14 @@ impl InstallLaunchArgs {
pub struct AppLaunchArgs {
pub root: PathBuf, // what should be the initial root
pub file_export_path: Option<String>, // where to write the produced path (if required with --out)
- pub cmd_export_path: Option<String>, // where to write the produced command (if required with --outcmd)
- pub tree_options: TreeOptions, // initial tree options
- pub commands: Option<String>, // commands passed as cli argument, still unparsed
- pub height: Option<u16>, // an optional height to replace the screen's one
- pub no_style: bool, // whether to remove all styles (including colors)
+ pub cmd_export_path: Option<String>, // where to write the produced command (if required with --outcmd)
+ pub tree_options: TreeOptions, // initial tree options
+ pub commands: Option<String>, // commands passed as cli argument, still unparsed
+ pub height: Option<u16>, // an optional height to replace the screen's one
+ pub no_style: bool, // whether to remove all styles (including colors)
+
+ #[cfg(feature="client-server")]
+ pub listen: Option<String>,
}
#[cfg(not(windows))]
@@ -182,6 +186,23 @@ pub fn run() -> Result<Option<Launchable>, ProgramError> {
let root = get_root_path(&cli_matches)?;
+ #[cfg(feature="client-server")]
+ if let Some(server_name) = cli_matches.value_of("send") {
+ use crate::{
+ command::Sequence,
+ net::{Client, Message},
+ };
+ let message = if let Some(seq) = &commands {
+ Message::Sequence(Sequence::new_local(seq.to_string()))
+ } else {
+ Message::Command(format!(":focus {}", root.to_string_lossy()))
+ };
+ let client = Client::new(server_name);
+ debug!("sending {:?}", &message);
+ client.send(&message)?;
+ return Ok(None);
+ }
+
let launch_args = AppLaunchArgs {
root,
file_export_path,
@@ -190,6 +211,9 @@ pub fn run() -> Result<Option<Launchable>, ProgramError> {
commands,
height,
no_style,
+
+ #[cfg(feature="client-server")]
+ listen: cli_matches.value_of("listen").map(str::to_string),
};
let context = AppContext::from(launch_args, verb_store, &config);
diff --git a/src/command/mod.rs b/src/command/mod.rs
index 98fe403..7655b92 100644
--- a/src/command/mod.rs
+++ b/src/command/mod.rs
@@ -11,7 +11,7 @@ pub use {
completion::Completions,
event::PanelInput,
parts::CommandParts,
- sequence::parse_command_sequence,
+ sequence::Sequence,
scroll::ScrollCommand,
trigger_type::TriggerType,
};
diff --git a/src/command/sequence.rs b/src/command/sequence.rs
index 3b51738..a8d8fe0 100644
--- a/src/command/sequence.rs
+++ b/src/command/sequence.rs
@@ -10,58 +10,95 @@ use {
},
};
-/// parse a string which is meant as a sequence of commands.
-///
-/// The ';' separator is used to identify inputs unless it's
-/// overriden in env variable BROOT_CMD_SEPARATOR.
-/// Verbs are verified, to ensure the command sequence has
-/// no unexpected holes.
-pub fn parse_command_sequence<'a>(
- sequence: &'a str,
- con: &AppContext,
-) -> Result<Vec<(&'a str, Command)>, ProgramError> {
- let separator = match std::env::var("BROOT_CMD_SEPARATOR") {
- Ok(sep) if !sep.is_empty() => sep,
- _ => String::from(";"),
- };
- debug!("Splitting cmd sequence with {:?}", separator);
- let mut commands = Vec::new();
- for input in sequence.split(&separator) {
- // an input may be made of two parts:
- // - a search pattern
- // - a verb followed by its arguments
- // we need to build a command for each part so
- // that the search is effectively done before
- // the verb invocation
- let raw_parts = CommandParts::from(input.to_string());
- let (pattern, verb_invocation) = raw_parts.split();
- if let Some(pattern) = pattern {
- debug!("adding pattern: {:?}", pattern);
- commands.push((input, Command::from_parts(pattern, false)));
+/// an unparsed sequence with its separator (which may be
+/// different from the one provided by local_separator())
+#[derive(Debug)]
+pub struct Sequence {
+ pub separator: String,
+ pub raw: String,
+}
+
+impl Sequence {
+ /// return the separator to use to parse sequences.
+ pub fn local_separator() -> String {
+ match std::env::var("BROOT_CMD_SEPARATOR") {
+ Ok(sep) if !sep.is_empty() => sep,
+ _ => String::from(";"),
}
- if let Some(verb_invocation) = verb_invocation {
- debug!("adding verb_invocation: {:?}", verb_invocation);
- let command = Command::from_parts(verb_invocation, true);
- if let Command::VerbInvocate(invocation) = &command {
- // we check that the verb exists to avoid running a sequence
- // of actions with some missing
- match con.verb_store.search(&invocation.name) {
- PrefixSearchResult::NoMatch => {
- return Err(ProgramError::UnknownVerb {
- name: invocation.name.to_string(),
- });
- }
- PrefixSearchResult::Matches(_) => {
- return Err(ProgramError::AmbiguousVerbName {
- name: invocation.name.to_string(),
- });
- }
- _ => {}
- }
- commands.push((input, command));
+ }
+ pub fn new(separator: String, raw: String) -> Self {
+ Self {
+ separator,
+ raw,
+ }
+ }
+ pub fn new_single(cmd: String) -> Self {
+ Self {
+ separator: "".to_string(),
+ raw: cmd,
+ }
+ }
+ pub fn new_local(raw: String) -> Self {
+ Self {
+ separator: Self::local_separator(),
+ raw,
+ }
+ }
+ pub fn parse(
+ &self,
+ con: &AppContext,
+ ) -> Result<Vec<(String, Command)>, ProgramError> {
+ debug!("Splitting cmd sequence with {:?}", &self.separator);
+ let mut commands = Vec::new();
+ if self.separator.is_empty() {
+ add_commands(&self.raw, &mut commands, con)?;
+ } else {
+ for input in self.raw.split(&self.separator) {
+ add_commands(input, &mut commands, con)?;
}
}
+ Ok(commands)
}
- Ok(commands)
}
+/// an input may be made of two parts:
+/// - a search pattern
+/// - a verb followed by its arguments
+/// we need to build a command for each part so
+/// that the search is effectively done before
+/// the verb invocation
+fn add_commands(
+ input: &str,
+ commands: &mut Vec<(String, Command)>,
+ con: &AppContext,
+) -> Result<(), ProgramError> {
+ let raw_parts = CommandParts::from(input.to_string());
+ let (pattern, verb_invocation) = raw_parts.split();
+ if let Some(pattern) = pattern {
+ debug!("adding pattern: {:?}", pattern);
+ commands.push((input.to_string(), Command::from_parts(pattern, false)));
+ }
+ if let Some(verb_invocation) = verb_invocation {
+ debug!("adding verb_invocation: {:?}", verb_invocation);
+ let command = Command::from_parts(verb_invocation, true);
+ if let Command::VerbInvocate(invocation) = &command {
+ // we check that the verb exists to avoid running a sequence
+ // of actions with some missing
+ match con.verb_store.search(&invocation.name) {
+ PrefixSearchResult::NoMatch => {
+ return Err(ProgramError::UnknownVerb {
+ name: invocation.name.to_string(),
+ });
+ }
+ PrefixSearchResult::Matches(_) => {
+ return Err(ProgramError::AmbiguousVerbName {
+ name: invocation.name.to_string(),
+ });
+ }
+ _ => {}
+ }
+ commands.push((input.to_string(), command));
+ }
+ }
+ Ok(())
+}
diff --git a/src/errors.rs b/src/errors.rs
index 640fb0e..55ad8ba 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -22,6 +22,7 @@ custom_error! {pub ProgramError
TerminalTooSmallError = "Terminal too small", // unable to open panel or app
InvalidGlobError {pattern: String} = "Invalid glob: {pattern}",
Unrecognized {token: String} = "Unrecognized: {token}",
+ NetError {source: NetError} = "{}",
}
custom_error! {pub TreeBuildError
@@ -30,7 +31,7 @@ custom_error! {pub TreeBuildError
}
custom_error! {pub ConfError
- Io {source: io::Error} = "unable to read from the file",
+ Io {source: io::Error} = "unable to read from the file: {}",
Toml {source: toml::de::Error} = "unable to parse TOML",
MissingField {txt: String} = "missing field in conf",
InvalidVerbInvocation {invocation: String} = "invalid verb invocation: {}",
@@ -57,3 +58,10 @@ custom_error! {pub InvalidSkinError
InvalidGreyLevel { level: u8 } = "grey level must be between 0 and 23 (got {})",
InvalidStyle {style: String} = "Invalid skin style : {}",
}
+
+custom_error! {pub NetError
+ SocketNotAvailable { path : String } = "Can't open socket: {} already exists - consider removing it",
+ Io {source: io::Error} = "error on the socket: {}",
+ InvalidMessage = "invalid message received",
+}
+
diff --git a/src/lib.rs b/src/lib.rs
index f433117..620f316 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -34,6 +34,10 @@ pub mod flag;
pub mod help;
pub mod keys;
pub mod launchable;
+
+#[cfg(feature="client-server")]
+pub mod net;
+
pub mod path;
pub mod path_anchor;
pub mod pattern;
diff --git a/src/net/client.rs b/src/net/client.rs
new file mode 100644
index 0000000..2e330e0
--- /dev/null
+++ b/src/net/client.rs
@@ -0,0 +1,32 @@
+
+use {
+ super::{
+ Message,
+ },
+ crate::{
+ errors::NetError,
+ },
+ std::{
+ os::unix::net::{
+ UnixStream,
+ },
+ },
+};
+
+pub struct Client {
+ path: String,
+}
+
+impl Client {
+ pub fn new(socket_name: &str) -> Self {
+ Self {
+ path: super::socket_file_path(socket_name),
+ }
+ }
+ pub fn send(&self, message: &Message) -> Result<(), NetError> {
+ debug!("try connecting {:?}", &self.path);
+ let mut stream = UnixStream::connect(&self.path)?;
+ message.write(&mut stream)?;
+ Ok(())
+ }
+}
diff --git a/src/net/message.rs b/src/net/message.rs
new file mode 100644
index 0000000..20d1ed5
--- /dev/null
+++ b/src/net/message.rs
@@ -0,0 +1,64 @@
+use {
+ crate::{
+ errors::NetError,
+ command::Sequence,
+ },
+ std::{
+ io::{
+ self,
+ BufRead,
+ Write,
+ },
+ },
+};
+
+/// A message which may be sent by a
+#[derive(Debug)]
+pub enum Message {
+ Command(String),
+ Hi,
+ //GetRoot
+ Sequence(Sequence),
+}
+
+fn read_line<BR: BufRead>(r: &mut BR) -> Result<String, NetError> {
+ let mut line = String::new();
+ r.read_line(&mut line)?;
+ debug!("read line => {:?}", &line);
+ while line.ends_with('\n') || line.ends_with('\r') {
+ line.pop();
+ }
+ Ok(line)
+}
+
+impl Message {
+ pub fn read<BR: BufRead>(r: &mut BR) -> Result<Self, NetError> {
+ // the first line gives the type of message
+ match read_line(r)?.as_ref() {
+ "CMD" => Ok(Self::Command(
+ read_line(r)?,
+ )),
+ "SEQ" => Ok(Self::Sequence(Sequence::new(
+ read_line(r)?,
+ read_line(r)?,
+ ))),
+ _ => Err(NetError::InvalidMessage),
+ }
+ }
+ pub fn write<W: Write>(&self, w: &mut W) -> io::Result<()> {
+ match self {
+ Self::Command(c) => {
+ writeln!(w, "CMD")?;
+ writeln!(w, "{}", c)
+ }
+ Self::Hi => {
+ writeln!(w, "HI")
+ }
+ Self::Sequence(Sequence { separator, raw }) => {
+ writeln!(w, "SEQ")?;
+ writeln!(w, "{}", separator)?;
+ writeln!(w, "{}", raw)
+ }
+ }
+ }
+}
diff --git a/src/net/mod.rs b/src/net/mod.rs
new file mode 100644
index 0000000..9610c6e
--- /dev/null
+++ b/src/net/mod.rs
@@ -0,0 +1,15 @@
+
+
+mod client;
+mod message;
+mod server;
+
+pub use {
+ client::Client,
+ message::Message,
+ server::Server,
+};
+
+pub fn socket_file_path(server_name: &str) -> String {
+ format!("/tmp/broot-server-{}.sock", server_name)
+}
diff --git a/src/net/server.rs b/src/net/server.rs
new file mode 100644
index 0000000..3ae5247
--- /dev/null
+++ b/src/net/server.rs
@@ -0,0 +1,75 @@
+use {
+ super::Message,
+ crate::{
+ errors::NetError,
+ command::Sequence,
+ },
+ crossbeam::channel::Sender,
+ std::{
+ fs,
+ io::BufReader,
+ os::unix::net::UnixListener,
+ thread,
+ },
+};
+
+pub struct Server {
+ path: String,
+}
+
+impl Server {
+ pub fn new(name: &str, tx: Sender<Sequence>) -> Result<Self, NetError> {
+ let path = super::socket_file_path(name);
+ if fs::metadata(&path).is_ok() {
+ return Err(NetError::SocketNotAvailable { path });
+ }
+ let listener = UnixListener::bind(&path)?;
+ debug!("listening on {}", &path);
+
+ // we use only one thread as we don't want to support long connections
+ thread::spawn(move || {
+ for stream in listener.incoming() {
+ match stream {
+ Ok(stream) => {
+ let mut br = BufReader::new(stream);
+ if let Some(sequence) = match Message::read(&mut br) {
+ Ok(Message::Command(command)) => {
+ debug!("got single command {:?}", &command);
+ // we convert it to a sequence
+ Some(Sequence::new_single(command))
+ }
+ Ok(Message::Sequence(sequence)) => {
+ debug!("got sequence {:?}", &sequence);
+ Some(sequence)
+ }
+ Ok(message) => {
+ debug!("got something not yet handled: {:?}", message);
+ None
+ }
+ Err(e) => {
+ warn!("Read error : {:?}", e);
+ None
+ }
+ } {
+ if let Err(e) = tx.send(sequence) {
+ warn!("error while sending {:?}", e);
+ return;
+ }
+ }
+ }
+ Err(e) => {
+ warn!("Stream error : {:?}", e);
+ }
+ }
+ }
+ });
+ Ok(Self { path })
+ }
+}
+
+impl Drop for Server {
+ fn drop(&mut self) {
+ debug!("removing socket file");
+ fs::remove_file(&self.path).unwrap();
+ }
+}
diff --git a/src/task_sync.rs b/src/task_sync.rs
index c1fbd6f..fcd121d 100644
--- a/src/task_sync.rs
+++ b/src/task_sync.rs
@@ -4,6 +4,11 @@ use {
termimad::Event,
};
+pub enum Either<A, B> {
+ First(A),
+ Second(B),
+}
+
#[derive(Debug, Clone)]
pub enum ComputationResult<V> {
NotComputed, // not computed but will probably be
@@ -129,6 +134,30 @@ impl Dam {
}
}
}
+
+ // or maybed return either Option<Event> or Option<T> ?
+ pub fn next<T>(&mut self, other: &Receiver<T>) -> Either<Option<Event>, Option<T>> {
+ if self.in_dam.is_some() {
+ Either::First(self.in_dam.take())
+ } else {
+ select! {
+ recv(self.receiver) -> event => Either::First(match event {
+ Ok(event) => Some(event),
+ Err(_) => {
+ debug!("dead dam"); // should be logged once
+ None
+ }
+ }),
+ recv(other) -> o => Either::Second(match o {
+ Ok(o) => Some(o),
+ Err(_) => {
+ debug!("dead other");
+ None
+ }
+ }),
+ }
+ }
+ }
}
pub struct DamObserver {