diff --git a/Cargo.lock b/Cargo.lock index 269d2c9..36ab37a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -314,6 +314,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.22.0" @@ -553,6 +564,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "pretty_env_logger" version = "0.4.0" @@ -599,6 +616,47 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -870,6 +928,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "rand", +] + [[package]] name = "version_check" version = "0.9.2" @@ -893,8 +960,15 @@ dependencies = [ "smallmap", "termprogress", "tokio", + "uuid", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "winapi" version = "0.2.8" diff --git a/Cargo.toml b/Cargo.toml index c536df9..9f35634 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ smallmap = "^1.1.6" log = "0.4.11" pretty_env_logger = "0.4.0" termprogress = "0.3.4" +uuid = {version = "0.8.1", features=["v4"]} [build-dependencies] rustc_version = "0.2" diff --git a/src/ext.rs b/src/ext.rs index 6aa7382..85d8525 100644 --- a/src/ext.rs +++ b/src/ext.rs @@ -11,6 +11,33 @@ use std::{ num::NonZeroU8, }; +pub trait JoinStrsExt: Sized +{ + /// Join an iterator of `str` with a seperator + fn join(self, with: &str) -> String; +} + +impl JoinStrsExt for I +where I: Iterator, + T: AsRef +{ + fn join(self, with: &str) -> String + { + let mut output = String::new(); + let mut first=true; + for string in self + { + if !first { + output.push_str(with); + } + let string = string.as_ref(); + output.push_str(string); + first=false; + } + output + } +} + pub use dedup::DedupIterExt; /// Iterator that maps `T` -> `U` @@ -188,3 +215,11 @@ impl IgnoreResultExt for Result //Do nothing } } + +impl IgnoreResultExt for Option +{ + #[inline(always)] fn ignore(self) + { + //Do nothing + } +} diff --git a/src/main.rs b/src/main.rs index 2d08f98..5135038 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ -#![cfg_attr(nightly, feature(never_type))] +#![cfg_attr(nightly, feature(never_type))] +#![cfg_attr(nightly, feature(drain_filter))] #![allow(dead_code)] diff --git a/src/progress/mod.rs b/src/progress/mod.rs index 955b22c..9bf59bf 100644 --- a/src/progress/mod.rs +++ b/src/progress/mod.rs @@ -26,6 +26,8 @@ use std::{ error, }; +mod tasklist; + /// Command to send to worker task. #[derive(Debug, Clone)] pub enum CommandKind @@ -41,6 +43,8 @@ pub enum CommandKind Many(Vec), } +pub type Response = Option>; + #[derive(Debug)] enum CommandIter { @@ -77,6 +81,7 @@ impl CommandKind /// Enumerate all possible commands if this is `Many`. /// /// The outputs may still contain `Many`. + //TODO: Make this work recursively fn enumerate(self) -> CommandIter { match self { @@ -90,7 +95,7 @@ impl CommandKind pub struct BarRef(Arc>); #[derive(Debug)] -struct Command(CommandKind, oneshot::Sender<()>); +struct Command(CommandKind, oneshot::Sender); /// The bar's state #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] @@ -148,7 +153,7 @@ impl Handle /// Send a command to the worker. /// /// Returns a future that completes to `Ok` when the worker successfully processes the command, and `Err` if the worker exits before processing it - pub async fn send_command(&mut self, command: CommandKind) -> Result>, WorkerCommError> + pub async fn send_command(&mut self, command: CommandKind) -> Result>, WorkerCommError> { let (tx, rx) = oneshot::channel(); self.chan.send(Command(command, tx)).await.map_err(|_| WorkerCommError)?; @@ -157,7 +162,7 @@ impl Handle } /// Send a command to the worker and then wait for it to be processed - pub async fn send_command_and_wait(&mut self, command: CommandKind) -> Result<(), WorkerCommError> + pub async fn send_command_and_wait(&mut self, command: CommandKind) -> Result { self.send_command(command).await?.await } @@ -241,7 +246,35 @@ pub fn host(bar: B) -> (Handle, JoinH () => {}; } while let Some(Command(command, response)) = rx.recv().await { - let _response = util::defer(move || response.send(()).ignore()); + let response = Arc::new(std::sync::Mutex::new(Some(response))); + + /// Send a response if one has not already been sent. + /// + /// # Returns + /// * `Some(Ok(())` - if response was sent okay + /// * `Some(Err(_))` - if response failed to send. + /// * `None` - if response has already been sent + /// + /// # Panics + /// If mutex is poisoned (this should be impossible). + macro_rules! send_response { + ($value:expr) => (send_response!(@ response => Some(Box::new($value)))); + (@ $response:ident => $value:expr) => { + { + if let Some(response) = $response.lock().unwrap().take() { + Some(response.send($value)) + } else { + None + } + } + }; + } + + // Guard that ensures a `None` response is sent after this command has been processed, if an explicit response has not yet been sent. + let _resp = { + let response = Arc::clone(&response); + util::defer(move || send_response!(@ response => None).ignore()) + }; match command { CommandKind::Shutdown => break, CommandKind::BumpHigh(sz) if sz >= 0 => { @@ -272,6 +305,8 @@ pub fn host(bar: B) -> (Handle, JoinH }, CommandKind::Line(line) => update_bar!(write line), CommandKind::LineErr(line) => update_bar!(write error line), + + //TODO: Title CommandKind::Many(_) => unimplemented!(), } } diff --git a/src/progress/tasklist.rs b/src/progress/tasklist.rs new file mode 100644 index 0000000..99ebfdd --- /dev/null +++ b/src/progress/tasklist.rs @@ -0,0 +1,140 @@ +//! Tasklist for progressbar +use super::*; +use std::{ + collections::LinkedList, + fmt, + error, +}; +use uuid::Uuid; + +#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[repr(transparent)] +pub struct TaskId(Uuid); + +impl fmt::Display for TaskId +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "<{}>", self.0) + } +} + + +/// A list of tasks +#[derive(Debug, Clone)] +pub struct TaskList +{ + tasks: LinkedList<(Uuid, String)>, + + strbuf: String, +} + +impl fmt::Display for TaskList +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", self.strbuf) + } +} + +impl TaskList +{ + /// The current full string + pub fn as_str(&self) -> &str + { + self.strbuf.as_str() + } + + /// Create a new, empty tasklist + pub fn new() -> Self + { + Self{tasks:LinkedList::new(), strbuf:String::new()} + } + + fn recalc_buf(&mut self) + { + self.strbuf = self.tasks.iter().map(|(_, stri)| stri.as_str()).join(", "); + } + + fn push_buf_one(&mut self, task: &str) + { + if self.strbuf.len() > 0 { + self.strbuf.push_str(", "); + } + self.strbuf.push_str(task) + } + + /// Add a task to the end of the list + pub fn add(&mut self, task: String) -> TaskId + { + let id = Uuid::new_v4(); + + self.push_buf_one(&task[..]); + self.tasks.push_back((id.clone(), task)); + + TaskId(id) + } + + /// Remove all tasks + pub fn clear(&mut self) + { + self.tasks.clear(); + self.strbuf.clear(); + } + + /// An iterator over all tasks currently in + pub fn tasks(&self) -> impl Iterator + '_ + { + self.tasks.iter().map(|(_, strs)| strs.as_str()) + } + + /// Remove this task from the list, returning its string if it exists + pub fn remove(&mut self, task_id: &TaskId) -> Result + { + let value = match self.tasks.drain_filter(|(id, _)| id==&task_id.0).next() { + Some((_, string)) => string, + None => return Err(IdNotFoundError(TaskId(task_id.0.clone()))), + }; + self.recalc_buf(); + Ok(value) + } + + /// Get this task ID's string + pub fn task_get(&self, task_id: &TaskId)-> Option<&str> + { + self.tasks.iter().filter(|(id, _)| id == &task_id.0).next().map(|x| x.1.as_str()) + } + + /// Replace this task ID with this string, retuning the old one. + pub fn task_set(&mut self, task_id: &TaskId, value: impl Into) -> Result + { + let old = match self.tasks.iter_mut().filter(|(id, _)| id == &task_id.0).next().map(|x| &mut x.1) { + Some(string) => std::mem::replace(string, value.into()), + None => return Err(IdNotFoundError(TaskId(task_id.0.clone()))), + }; + self.recalc_buf(); + Ok(old) + } +} + +/// Error when trying to remove a non-existent ID. +#[derive(Debug)] +pub struct IdNotFoundError(TaskId); + +impl IdNotFoundError +{ + /// Get the ID that was not found + pub fn id(&self) -> &TaskId + { + &self.0 + } +} + +impl error::Error for IdNotFoundError{} +impl fmt::Display for IdNotFoundError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}: unknown ID to this TaskList", self.0) + } +}