rmidimap/src/run.rs
2024-12-30 16:10:30 +01:00

176 lines
5 KiB
Rust

use std::sync::mpsc;
use std::sync::{Arc, Mutex};
use std::thread;
use libc::SIGUSR1;
use signal_hook::iterator::Signals;
use crate::config::{Config, DeviceConfig};
use crate::eventmap::EventMap;
use crate::midi::builder::builder;
use crate::midi::{Builder, MidiInputHandler, MidiPort, PortFilter};
use crate::util::InternalTryFrom;
use crate::{constant, Error};
type DeviceRunItem<'a> = (
&'a DeviceConfig,
EventMap<'a>,
Option<Arc<Mutex<(u32, u32)>>>,
);
type DeviceRunResult<'a> = (
thread::ScopedJoinHandle<'a, Result<(), Error>>,
mpsc::Sender<bool>,
);
pub fn cross_shell(cmd: &str) -> Vec<String> {
if cfg!(target_os = "windows") {
vec!["cmd", "/C", cmd]
} else {
vec!["sh", "-c", cmd]
}
.iter()
.map(|x| x.to_string())
.collect()
}
builder!(ListDevicesBuilder, list_devices, (), Result<(), Error>);
builder!(RunConfigBuilder, run_config, &Config, Result<(), Error>);
pub fn list_devices<T>(input: &T, _: ()) -> Result<(), Error>
where
T: MidiInputHandler + Send + 'static,
<T as MidiInputHandler>::DeviceAddr: 'static + std::fmt::Display,
{
let ports = MidiInputHandler::ports(input)?;
println!(" Addr\t Name");
for p in ports {
println!("{}", p);
}
Ok(())
}
pub fn run_config<T>(input: &T, conf: &Config) -> Result<(), Error>
where
T: MidiInputHandler + Send + 'static,
<T as MidiInputHandler>::DeviceAddr: 'static + std::fmt::Display + InternalTryFrom<String>,
{
let cfevmap: Vec<DeviceRunItem> = conf
.devices
.iter()
.map(|x| {
(
x,
EventMap::from(x),
x.max_connections.map(|v| (Arc::new(Mutex::new((0, v))))),
)
})
.collect();
let (tdev, rdev) = mpsc::channel::<Option<MidiPort<T::DeviceAddr>>>();
let (tsd, rsd) = mpsc::channel::<bool>();
let ntsd = tsd.clone();
let ntdev = tdev.clone();
let mut signals = Signals::new(&[SIGUSR1])?;
let _signal_thread = thread::spawn(move || {
for sig in signals.forever() {
match sig {
10 => {
println!("Recieved SIGUSR1, reloading config file");
ntsd.send(true).unwrap();
ntdev.send(None).unwrap();
break;
}
_ => (),
}
}
});
thread::scope(|s| -> Result<(), Error> {
let mut threads: Vec<DeviceRunResult> = Vec::new();
let ports = input.ports()?;
for p in ports {
if let Some(v) = try_connect_process(input, s, &p, &cfevmap)? {
threads.push(v)
}
}
let event_thread = s.spawn(move || {
let mut input = T::new(constant::CLIENT_NAME_EVENT).unwrap();
let r = input.device_events(tdev.clone(), (tsd, rsd));
tdev.send(None).unwrap();
r
});
loop {
let p = rdev.recv()?;
if p.is_none() {
break;
}
let p = p.unwrap();
if conf.log {
println!("{}: device connect: {}", constant::CLIENT_NAME, p);
}
if let Some(v) = try_connect_process(input, s, &p, &cfevmap)? {
threads.push(v)
}
}
event_thread.join().unwrap()?;
for (thread, ss) in threads {
let _ = ss.send(true);
let _ = thread
.join()
.unwrap()
.map_err(|e| eprintln!("WARN: error in thread: {}", e));
}
Ok(())
})?;
Ok(())
}
fn try_connect_process<'a, T>(
input: &T,
s: &'a thread::Scope<'a, '_>,
p: &MidiPort<T::DeviceAddr>,
cfevmap: &'a [DeviceRunItem<'a>],
) -> Result<Option<DeviceRunResult<'a>>, Error>
where
T: MidiInputHandler + Send + 'static,
<T as MidiInputHandler>::DeviceAddr: 'static + InternalTryFrom<String>,
{
for (dev, eventmap, counter) in cfevmap {
// device counter is full
if let Some(m) = counter {
let m = m.lock().unwrap();
if m.0 >= m.1 {
continue;
}
}
if let Some(mut c) = input.try_connect(p.clone(), PortFilter::i_try_from(*dev)?)? {
// increase device counter
if let Some(m) = counter {
let mut m = m.lock().unwrap();
m.0 += 1;
}
// stop signal channel
let (sts, srs) = mpsc::channel::<bool>();
let mm = counter.as_ref().map(Arc::clone);
let nsts = sts.clone();
let t = s.spawn(move || -> Result<(), Error> {
dev.run_connect()?;
// blocking process
c.run(dev, eventmap, (nsts, srs))?;
// decrease device counter
if let Some(m) = mm {
let mut m = m.lock().unwrap();
m.0 -= 1;
}
dev.run_disconnect()?;
Ok(())
});
return Ok(Some((t, sts)));
}
}
Ok(None)
}