use std::mem;
use color_eyre::Report;
use log::{error, warn};
use nusb::{DeviceInfo, list_devices};
use crate::BmpParams;
use crate::bmp::{BmpDevice, BmpPlatform};
use crate::error::ErrorKind;
use crate::usb::{Pid, PortId, Vid};
#[derive(Debug, Clone, Default)]
pub struct BmpMatcher
{
index: Option<usize>,
serial: Option<String>,
port: Option<PortId>,
}
enum MatchResult
{
NoMatch(DeviceInfo),
Found(BmpDevice),
Error(Report),
}
impl BmpMatcher
{
pub fn new() -> Self
{
Default::default()
}
pub fn new_with_port(port: PortId) -> Self
{
Self::new().port(Some(port))
}
pub fn from_params<Params>(params: &Params) -> Self
where
Params: BmpParams,
{
Self::new()
.index(params.index())
.serial(params.serial_number())
.port(None)
}
#[must_use]
pub fn index(mut self, idx: Option<usize>) -> Self
{
self.index = idx;
self
}
#[must_use]
pub fn serial<'s, IntoOptStrT>(mut self, serial: IntoOptStrT) -> Self
where
IntoOptStrT: Into<Option<&'s str>>,
{
self.serial = serial.into().map(|s| s.to_string());
self
}
#[must_use]
pub fn port(mut self, port: Option<PortId>) -> Self
{
self.port = port;
self
}
pub fn get_index(&self) -> Option<usize>
{
self.index
}
pub fn get_serial(&self) -> Option<&str>
{
self.serial.as_deref()
}
pub fn get_port(&self) -> Option<PortId>
{
self.port.clone()
}
pub fn find_matching_probes(&self) -> BmpMatchResults
{
let mut results = BmpMatchResults {
found: Vec::new(),
filtered_out: Vec::new(),
errors: Vec::new(),
};
let devices = match list_devices() {
Ok(d) => d,
Err(e) => {
results.errors.push(e.into());
return results;
},
};
let devices = devices.filter(|dev| {
let vid = dev.vendor_id();
let pid = dev.product_id();
BmpPlatform::from_vid_pid(Vid(vid), Pid(pid)).is_some()
});
devices
.enumerate()
.map(|(index, device_info)| self.matching_probe(index, device_info))
.collect()
}
fn matching_probe(&self, index: usize, device_info: DeviceInfo) -> MatchResult
{
let serial_matches = self
.serial
.as_deref()
.is_none_or(|s| Some(s) == device_info.serial_number());
let index_matches = self.index.is_none_or(|needle| needle == index);
let port_matches = self.port.as_ref().is_none_or(|p| {
let port = PortId::new(&device_info);
p == &port
});
if index_matches && port_matches && serial_matches {
match BmpDevice::from_usb_device(device_info) {
Ok(bmpdev) => MatchResult::Found(bmpdev),
Err(e) => MatchResult::Error(e),
}
} else {
MatchResult::NoMatch(device_info)
}
}
}
#[derive(Debug, Default)]
pub struct BmpMatchResults
{
pub found: Vec<BmpDevice>,
pub filtered_out: Vec<DeviceInfo>,
pub errors: Vec<Report>,
}
impl FromIterator<MatchResult> for BmpMatchResults
{
fn from_iter<I: IntoIterator<Item = MatchResult>>(iter: I) -> Self
{
let mut results = BmpMatchResults {
found: Vec::new(),
filtered_out: Vec::new(),
errors: Vec::new(),
};
for match_result in iter {
match match_result {
MatchResult::NoMatch(device_info) => results.filtered_out.push(device_info),
MatchResult::Found(bmpdev) => results.found.push(bmpdev),
MatchResult::Error(e) => results.errors.push(e),
};
}
results
}
}
impl BmpMatchResults
{
pub fn pop_all(&mut self) -> Result<Vec<BmpDevice>, ErrorKind>
{
match self.found.len() {
0 => {
match self.filtered_out.len() {
0 => {},
1 => {
match BmpDevice::from_usb_device(
self.filtered_out
.pop()
.expect("The length check makes this a guaranteed assumption"),
) {
Ok(bmpdev) => warn!(
"Matching device not found, but and the following Black Magic Probe device was \
filtered out: {}",
&bmpdev
),
Err(_) => {
warn!("Matching device not found but 1 Black Magic Probe device was filtered out.")
},
};
},
drained_len => {
warn!(
"Matching devices not found but {} Black Magic Probe devices were filtered out.",
drained_len
);
warn!("Filter arguments (--serial, --index, --port) may be incorrect.");
},
}
self.filtered_out.clear();
if !self.errors.is_empty() {
warn!("Device not found and errors occurred when searching for devices.");
warn!(
"One of these may be why the Black Magic Probe device was not found: {:?}",
self.errors.as_slice()
);
}
error!("{}", ErrorKind::DeviceNotFound);
Err(ErrorKind::DeviceNotFound)
},
_ => {
if !self.errors.is_empty() {
warn!("Matching device found but errors occurred when searching for devices.");
warn!("It is unlikely but possible that the incorrect device was selected!");
warn!("Other device errors: {:?}", self.errors.as_slice());
}
Ok(mem::take(&mut self.found))
},
}
}
pub fn pop_single(&mut self, operation: &str) -> Result<BmpDevice, ErrorKind>
{
match self.found.len() {
0 => {
if !self.filtered_out.is_empty() {
let (suffix, verb) = if self.filtered_out.len() > 1 {
("s", "were")
} else {
("", "was")
};
warn!(
"Matching device not found and {} Black Magic Probe device{} {} filtered out.",
self.filtered_out.len(),
suffix,
verb,
);
warn!("Filter arguments (--serial, --index, --port may be incorrect.");
}
if !self.errors.is_empty() {
warn!("Device not found and errors occurred when searching for devices.");
warn!(
"One of these may be why the Black Magic Probe device was not found: {:?}",
self.errors.as_slice()
);
}
error!("{}", ErrorKind::DeviceNotFound);
Err(ErrorKind::DeviceNotFound)
},
1 => {
if !self.errors.is_empty() {
warn!("Matching device found but errors occurred when searching for devices.");
warn!("It is unlikely but possible that the incorrect device was selected!");
warn!("Other device errors: {:?}", self.errors.as_slice());
}
Ok(self.found.remove(0))
},
found_len => {
error!(
"{} operation only accepts one Black Magic Probe device, but {} were found!",
operation, found_len
);
error!("Hint: try bmputil info and revise your filter arguments (--serial, --index, --port).");
Err(ErrorKind::TooManyDevices)
},
}
}
pub(crate) fn pop_single_silent(&mut self) -> color_eyre::Result<BmpDevice, ErrorKind>
{
match self.found.len() {
0 => Err(ErrorKind::DeviceNotFound),
1 => Ok(self.found.remove(0)),
_ => Err(ErrorKind::TooManyDevices),
}
}
}