use std::fs;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::time::Duration;
use color_eyre::eyre::{OptionExt, Result};
use dialoguer::Select;
use dialoguer::theme::ColorfulTheme;
use directories::ProjectDirs;
use indicatif::ProgressBar;
use log::warn;
use crate::bmp::{BmpDevice, BmpMatcher};
use crate::firmware_selector::FirmwareMultichoice;
use crate::metadata::download_metadata;
use crate::metadata::structs::{Firmware, FirmwareDownload, Metadata};
use crate::probe_identity::{ProbeIdentity, VersionNumber};
use crate::{BmpParams, FlashParams, flasher};
pub fn switch_firmware<Params>(params: &Params, paths: &ProjectDirs) -> Result<()>
where
Params: BmpParams + FlashParams,
{
let probe = match select_probe(params)? {
Some(probe) => probe,
None => {
println!("Black Magic Debug probe selection cancelled, stopping operation");
return Ok(());
},
};
let identity = probe.firmware_identity()?;
println!("Probe {} ({}) selected for firmware update", identity, probe.serial_number()?);
let cache = paths.cache_dir();
let metadata = download_metadata(cache)?;
let (release, firmware) = match pick_release(&metadata, identity)? {
Some(firmware) => firmware,
None => {
println!("firmware release selection cancelled, stopping operation");
return Ok(());
},
};
let firmware_variant = match pick_firmware(release, firmware)? {
Some(variant) => variant,
None => {
println!("firmware variant selection cancelled, stopping operation");
return Ok(());
},
};
let elf_file = download_firmware(firmware_variant, cache)?;
flasher::flash_probe(params, probe, elf_file)
}
fn select_probe<Params>(params: &Params) -> Result<Option<BmpDevice>>
where
Params: BmpParams,
{
let matcher = BmpMatcher::from_params(params);
let mut results = matcher.find_matching_probes();
let mut devices = results.pop_all().map_err(|kind| kind.error())?;
match devices.len() {
0 => panic!("This state shouldn't happen, because devices should contain at least 1"),
1 => Ok(Some(devices.remove(0))),
_ => {
let items: Vec<_> = devices
.iter()
.flat_map(|device| -> Result<String> {
Ok(format!("{} ({})", device.firmware_identity()?, device.serial_number()?))
})
.collect();
let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which probe would you like to change the firmware on?")
.items(items.as_slice())
.report(false)
.interact_opt()?;
Ok(selection.map(|index| devices.remove(index)))
},
}
}
fn pick_release(metadata: &Metadata, identity: ProbeIdentity) -> Result<Option<(&str, &Firmware)>>
{
let variant = identity
.variant()
.ok_or_eyre("Device appears to be in bootloader, so cannot determine probe type")?;
let firmware_version = match &identity.version {
VersionNumber::Unknown => {
warn!("Old firmware version is detected, pretending this is version 'v1.6'");
"v1.6".into()
},
VersionNumber::Invalid | VersionNumber::GitHash(_) => {
warn!("Firmware is of an unknown version");
"".into()
},
VersionNumber::FullVersion(parts) => parts.to_string(),
};
let releases: Vec<_> = metadata
.releases
.iter()
.filter(|&(version, release)| {
!(firmware_version.as_str() == version && release.firmware[&variant].variants.len() == 1) &&
release.firmware.contains_key(&variant)
})
.collect();
let mut items: Vec<_> = releases.iter().map(|&(version, _)| version).collect();
items.sort_by(|a, b| a.cmp(b).reverse());
let selection = match Select::with_theme(&ColorfulTheme::default())
.with_prompt("Which release would you like to run on your probe?")
.items(items.as_slice())
.interact_opt()?
{
Some(release) => release,
None => return Ok(None),
};
Ok(Some((
items[selection].as_str(),
&metadata.releases[items[selection].as_str()].firmware[&variant],
)))
}
pub fn pick_firmware<'a>(release: &'a str, firmware: &'a Firmware) -> Result<Option<&'a FirmwareDownload>>
{
match firmware.variants.len() {
0 => Ok(None),
1 => {
let (_, firmware) = firmware.variants.iter().next().unwrap();
println!("Using firmware {}", firmware.friendly_name);
Ok(Some(firmware))
},
_ => {
let mut chooser = FirmwareMultichoice::new(release, &firmware.variants);
while !chooser.complete() {
chooser.step()?;
}
Ok(chooser.selection())
},
}
}
pub fn download_firmware(variant: &FirmwareDownload, cache_path: &Path) -> Result<PathBuf>
{
fs::create_dir_all(cache_path)?;
let file_name = &variant.file_name;
let cache_file_name = cache_path.join(file_name);
if cache_file_name.exists() {
return Ok(cache_file_name);
}
let progress = ProgressBar::new_spinner().with_message("Downloading requested firmware");
progress.enable_steady_tick(Duration::from_millis(100));
let client = reqwest::blocking::Client::new();
let mut response = client.get(variant.uri.clone())
.timeout(Duration::from_secs(2))
.send()?
.error_for_status()?;
let mut cache_file = File::create(cache_file_name.as_path())?;
response.copy_to(&mut cache_file)?;
progress.finish();
Ok(cache_file_name)
}