#[cfg(all(feature = "json-fmt", not(feature = "toml-fmt")))]
use serde_json as serde_fmt;
#[cfg(not(any(feature = "toml-fmt", feature = "json-fmt")))]
use serde_yaml as serde_fmt;
#[cfg(all(feature = "toml-fmt", not(feature = "json-fmt")))]
use toml as serde_fmt;
use tokio::prelude::stream::{self, Stream};
use dirs;
use std::fs::File;
use std::io::{Error as IOError, ErrorKind, Read};
use std::path::Path;
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
use crate::components::{Component, ComponentID, ComponentStream};
use crate::config::Config;
use crate::event::Event;
const PATH_LOAD_ORDER: [&str; 3] = [
"{config}/{name}.{ext}",
"{home}/.{name}.{ext}",
"/etc/{name}/{name}.{ext}",
];
#[derive(Debug)]
pub struct Bar {
error_count: usize,
config: Arc<Mutex<Config>>,
events: Option<(Sender<ComponentID>, Receiver<ComponentID>)>,
}
impl Bar {
pub fn load<T: Read>(mut config_file: T) -> Result<Self, IOError> {
let mut content = String::new();
config_file.read_to_string(&mut content)?;
let config =
serde_fmt::from_str(&content).map_err(|e| IOError::new(ErrorKind::InvalidData, e))?;
Ok(Bar {
events: None,
error_count: 0,
config: Arc::new(Mutex::new(config)),
})
}
pub fn recv(&mut self) -> ComponentID {
if self.events.is_none() {
self.events = Some(self.start_loop());
}
self.events.as_ref().unwrap().1.recv().unwrap()
}
pub fn try_recv(&mut self) -> Option<ComponentID> {
if self.events.is_none() {
self.events = Some(self.start_loop());
}
match self.events.as_ref().unwrap().1.try_recv() {
Ok(comp_id) => Some(comp_id),
Err(TryRecvError::Empty) => None,
Err(e) => Err(e).unwrap(),
}
}
pub fn lock(&self) -> MutexGuard<Config> {
self.config.lock().unwrap()
}
pub fn notify(&mut self, event: Event) {
let mut config = self.lock();
let mut dirty_comps = Vec::new();
let mut notify = |comps: &mut Vec<Box<Component>>| {
for comp in comps {
if comp.notify(event) {
dirty_comps.push(comp.id());
}
}
};
notify(&mut config.left);
notify(&mut config.center);
notify(&mut config.right);
drop(config);
if let Some((ref events_tx, _)) = self.events {
for comp_id in dirty_comps {
events_tx.send(comp_id).unwrap();
}
}
}
fn start_loop(&self) -> (Sender<ComponentID>, Receiver<ComponentID>) {
let (events_tx, events_rx) = mpsc::channel();
let bar_events_tx = events_tx.clone();
let config = self.config.clone();
thread::spawn(move || {
let combined = {
let config = config.lock().unwrap();
let mut combined: ComponentStream = Box::new(stream::empty());
for comp in config
.left
.iter()
.chain(&config.center)
.chain(&config.right)
{
combined = Box::new(combined.select(comp.stream()));
}
combined
};
let combined = combined.for_each(move |comp_id| {
let mut config = config.lock().unwrap();
let update_comps = |comps: &mut Vec<Box<Component>>| {
if let Some(true) = comps
.iter_mut()
.find(|comp| comp_id == comp.id())
.map(|comp| comp.update())
{
events_tx.send(comp_id).unwrap();
true
} else {
false
}
};
let _ = update_comps(&mut config.left)
|| update_comps(&mut config.center)
|| update_comps(&mut config.right);
Ok(())
});
tokio::run(combined);
});
(bar_events_tx, events_rx)
}
}
pub fn config_file(name: &str) -> Result<File, IOError> {
for path in &PATH_LOAD_ORDER[..] {
let mut path = path.to_string();
#[cfg_attr(feature = "cargo-clippy", allow(ifs_same_cond))]
let extension = if cfg!(feature = "toml-fmt") && !cfg!(feature = "json-fmt") {
"toml"
} else if cfg!(feature = "json-fmt") && !cfg!(feature = "toml-fmt") {
"json"
} else {
"yml"
};
path = path.replace("{ext}", extension);
path = path.replace(
"{home}",
&dirs::home_dir()
.and_then(|p| Some(p.to_string_lossy().to_string()))
.unwrap_or_else(String::new),
);
path = path.replace(
"{config}",
&dirs::config_dir()
.and_then(|p| Some(p.to_string_lossy().to_string()))
.unwrap_or_else(String::new),
);
path = path.replace("{name}", name);
let metadata = Path::new(&path).metadata();
if let Ok(metadata) = metadata {
if metadata.is_file() {
return Ok(File::open(path)?);
}
}
}
Err(IOError::new(ErrorKind::NotFound, "no config file present"))
}