diff --git a/daemon/src/firmware/firmware_file.rs b/daemon/src/firmware/firmware_file.rs index 9f2e3739..0cbcfd52 100644 --- a/daemon/src/firmware/firmware_file.rs +++ b/daemon/src/firmware/firmware_file.rs @@ -1,16 +1,11 @@ use anyhow::{bail, Result}; use byteorder::{LittleEndian, ReadBytesExt}; +use goxlr_ipc::FirmwareInfo; use goxlr_types::{DeviceType, VersionNumber}; use std::io; use std::io::Cursor; use std::path::PathBuf; -pub struct FirmwareInfo { - pub path: PathBuf, - pub device_type: DeviceType, - pub version: VersionNumber, -} - pub fn check_firmware(path: &PathBuf) -> Result { load_firmware_file(path) } diff --git a/daemon/src/firmware/firmware_update.rs b/daemon/src/firmware/firmware_update.rs index bc4f8773..793b9616 100644 --- a/daemon/src/firmware/firmware_update.rs +++ b/daemon/src/firmware/firmware_update.rs @@ -1,8 +1,8 @@ -use crate::firmware::firmware_file::{check_firmware, FirmwareInfo}; +use crate::firmware::firmware_file::check_firmware; use crate::FIRMWARE_BASE; use anyhow::{bail, Result}; use futures_util::StreamExt; -use goxlr_ipc::UpdateState; +use goxlr_ipc::{FirmwareInfo, UpdateState}; use goxlr_types::{DeviceType, VersionNumber}; use log::{error, info}; use reqwest::Client; @@ -22,21 +22,25 @@ type OneShot = oneshot::Sender; const FAIL_BACK_FULL_FIRMWARE: &str = "GoXLR_Firmware.bin"; const FAIL_BACK_MINI_FIRMWARE: &str = "GoXLR_MINI_Firmware.bin"; +#[derive(Clone)] pub struct FirmwareUpdateSettings { pub sender: Sender, pub device: FirmwareUpdateDevice, pub file: Option, + pub force: bool, } +#[derive(Clone)] pub struct FirmwareUpdateDevice { pub serial: String, pub device_type: DeviceType, pub current_firmware: VersionNumber, } -pub async fn do_firmware_update(settings: FirmwareUpdateSettings) { + +pub async fn start_firmware_update(settings: FirmwareUpdateSettings) { info!("Beginning Firmware Update..."); let sender = settings.sender.clone(); - let device = settings.device; + let device = settings.device.clone(); if let Err(e) = set_update_state(&device.serial, sender.clone(), UpdateState::Starting).await { error!("Something's gone horribly wrong: {}", e); @@ -69,19 +73,24 @@ pub async fn do_firmware_update(settings: FirmwareUpdateSettings) { return; } - if file_info.version <= device.current_firmware && settings.file.is_none() { - // We're apparently downloaded this file, and it would cause a downgrade / reinstall - // which is unexpected. Bail out instead of proceeding. - let which = if file_info.version < device.current_firmware { - "older" - } else { - "the same" - }; - - let error = format!("Downloaded file is {} as current firmware.", which); - send_error(&device.serial, sender, error).await; - return; + // So we go either one of two ways here, if there's a problem, we set the update as 'Paused' with + // the file_info, and the UI can then send a 'Continue' if the user is happy with the info, otherwise + // we just go with the update. + if file_info.version <= device.current_firmware { + set_update_state( + &device.serial, + sender.clone(), + UpdateState::Pause(file_info), + ) + .await; + } else { + do_firmware_update(settings, file_info).await; } +} + +pub async fn do_firmware_update(settings: FirmwareUpdateSettings, file_info: FirmwareInfo) { + let sender = settings.sender.clone(); + let device = settings.device; // Ok, when we get here we should be good to go, grab the firmware bytes from disk.. let firmware = match fs::read(file_info.path) { diff --git a/daemon/src/primary_worker.rs b/daemon/src/primary_worker.rs index b5edd121..192cd758 100644 --- a/daemon/src/primary_worker.rs +++ b/daemon/src/primary_worker.rs @@ -2,8 +2,8 @@ use crate::device::Device; use crate::events::EventTriggers; use crate::files::extract_defaults; use crate::firmware::firmware_update::{ - do_firmware_update, FirmwareMessages, FirmwareRequest, FirmwareUpdateDevice, - FirmwareUpdateSettings, + do_firmware_update, start_firmware_update, FirmwareMessages, FirmwareRequest, + FirmwareUpdateDevice, FirmwareUpdateSettings, }; use crate::platform::{get_ui_app_path, has_autostart, set_autostart}; use crate::{ @@ -42,7 +42,8 @@ pub enum DeviceCommand { RunDaemonCommand(DaemonCommand, oneshot::Sender>), RunDeviceCommand(String, GoXLRCommand, oneshot::Sender>), GetDeviceMicLevel(String, oneshot::Sender>), - RunFirmwareUpdate(String, Option, oneshot::Sender>), + RunFirmwareUpdate(String, Option, bool, oneshot::Sender>), + ContinueFirmwareUpdate(String, oneshot::Sender>), ClearFirmwareState(String, oneshot::Sender>), } @@ -53,6 +54,12 @@ pub enum DeviceStateChange { Wake(oneshot::Sender<()>), } +#[derive(Clone)] +struct FirmwareUpdateState { + settings: FirmwareUpdateSettings, + status: FirmwareStatus, +} + pub type DeviceSender = Sender; pub type DeviceReceiver = Receiver; @@ -107,7 +114,7 @@ pub async fn spawn_usb_handler( // Create the Primary Device List, and 'Ignore' list.. let mut devices: HashMap = HashMap::new(); - let mut devices_firmware: HashMap> = HashMap::new(); + let mut devices_firmware: HashMap = HashMap::new(); let mut ignore_list = HashMap::new(); let mut files = get_files(&mut file_manager, &settings).await; @@ -129,48 +136,37 @@ pub async fn spawn_usb_handler( let mut change_found = false; tokio::select! { Some(version) = firmware_receiver.recv() => { - // Uncomment this for testing purposes! - // use enum_map::enum_map; - // let version = enum_map! { - // DeviceType::Mini => { - // Some(VersionNumber::from(String::from("0.0.0.0"))) - // }, - // DeviceType::Full => { - // Some(VersionNumber::from(String::from("0.0.0.0"))) - // }, - // DeviceType::Unknown => { - // Some(VersionNumber::from(String::from("0.0.0.0"))) - // } - // }; - firmware_version = Some(version); change_found = true; }, Some(received) = firmware_update_receiver.recv() => { match received { - FirmwareRequest::SetUpdateState(serial,state) => { - if let Some(Some(status)) = devices_firmware.get_mut(&serial) { - status.state = state; - status.progress = 0; + FirmwareRequest::SetUpdateState(serial,status) => { + if let Some(state) = devices_firmware.get_mut(&serial) { + state.status.state = status; + state.status.progress = 0; change_found = true; } else { // We don't have a state for this device, set one. - let state = FirmwareStatus { - state, + let status = FirmwareStatus { + state: status, progress: 0, error: None }; // Only create this if the serial is present.. if devices.contains_key(&serial) { - devices_firmware.insert(serial, Some(state)); + // Get the current status.. + let state = devices_firmware.get_mut(&serial).unwrap(); + state.status = status; + change_found = true; } } } FirmwareRequest::SetStateProgress(serial, progress) => { - if let Some(Some(status)) = devices_firmware.get_mut(&serial) { - status.progress = progress; + if let Some(state) = devices_firmware.get_mut(&serial) { + state.status.progress = progress; change_found = true; } else { error!("Update State does not exist! Ignoring."); @@ -178,8 +174,8 @@ pub async fn spawn_usb_handler( } FirmwareRequest::SetError(serial, error) => { debug!("Setting Error: {}", error); - if let Some(Some(status)) = devices_firmware.get_mut(&serial) { - status.error = Some(error); + if let Some(state) = devices_firmware.get_mut(&serial) { + state.status.error = Some(error); change_found = true; } else { error!("Update State does not exist! Ignoring.."); @@ -245,7 +241,6 @@ pub async fn spawn_usb_handler( let serial = String::from(device.serial()); devices.insert(serial.clone(), device); - devices_firmware.insert(serial, None); change_found = true; } Err(e) => { @@ -283,9 +278,16 @@ pub async fn spawn_usb_handler( Some(serial) = disconnect_receiver.recv() => { info!("[{}] Device Disconnected", serial); devices.remove(&serial); - if devices_firmware.contains_key(&serial) && devices_firmware.get(&serial).unwrap().is_none() { - // Only remove if we have no status (prevents the reboot from clearing the state) - devices_firmware.remove(&serial); + + // If this device was actively doing a firmware update that's not complete, we should scream + // INCREDIBLY loudly (in the logs).. We will keep this device around though, the error will + // be handled by the firmware updater, and presented accordingly. + if devices_firmware.contains_key(&serial) { + let state = devices_firmware.get(&serial).unwrap(); + match state.status.state { + UpdateState::Failed | UpdateState::Pause(_) | UpdateState::Complete => todo!(), + _ => warn!("DEVICE REMOVED BEFORE UPDATE COMPLETE") + } } change_found = true; }, @@ -476,7 +478,7 @@ pub async fn spawn_usb_handler( } }, - DeviceCommand::RunFirmwareUpdate(serial, file, sender) => { + DeviceCommand::RunFirmwareUpdate(serial, file, force, sender) => { if let Some(device) = devices.get_mut(&serial) { let device_type = device.get_hardware_type(); let current_firmware = device.get_firmware_version(); @@ -490,32 +492,44 @@ pub async fn spawn_usb_handler( current_firmware, }, file, + force, }; - tokio::spawn(do_firmware_update(update_settings)); + tokio::spawn(start_firmware_update(update_settings)); let _ = sender.send(Ok(())); } else { let _ = sender.send(Err(anyhow!("Device {} is not connected", serial))); } }, + DeviceCommand::ContinueFirmwareUpdate(serial, sender) => { + if let Some(state) = devices_firmware.get(&serial) { + match &state.status.state { + UpdateState::Pause(file_info) => { + tokio::spawn(do_firmware_update(state.settings.clone(), file_info.to_owned())); + let _ = sender.send(Ok(())); + } + _ => { + let _ = sender.send(Err(anyhow!("Update not in Paused State!"))); + } + } + } else { + let _ = sender.send(Err(anyhow!("Devite not performing firmware update."))); + } + }, DeviceCommand::ClearFirmwareState(serial, sender) => { - if let Some(device) = devices_firmware.get_mut(&serial) { - if let Some(status) = device { - if status.state != UpdateState::Complete && status.state != UpdateState::Failed { - let _ = sender.send(Err(anyhow!("Cannot Clear, update in progress"))); - } else { - device.take(); - - if !devices.contains_key(&serial) { - // If the device is no longer attached, remove it. - devices.remove(&serial); - } + if let Some(device) = devices_firmware.get(&serial) { + match device.status.state { + UpdateState::Complete | UpdateState::Failed | UpdateState::Pause(_) => { + // We're at a point where this update can be stopped, nuke it from the firmware list.. + devices_firmware.remove(&serial); + }, + _ => { + // Can't stop it just yet.. :D + let _ = sender.send(Err(anyhow!("Cannot Clear, update in progress.."))); } - } else { - let _ = sender.send(Err(anyhow!("Device not performing firmware update"))); - } + }; } else { - let _ = sender.send(Err(anyhow!("Device not Present"))); + let _ = sender.send(Err(anyhow!("Device not performing firmware update."))); } } } @@ -571,7 +585,7 @@ async fn get_daemon_status( http_settings: &HttpSettings, driver_details: &DriverDetails, firmware_versions: &Option>>, - firmware_state: &HashMap>, + firmware_state: &HashMap, files: Files, app_check: &Option, ) -> DaemonStatus { @@ -609,7 +623,9 @@ async fn get_daemon_status( }; for (serial, state) in firmware_state { - status.firmware.insert(serial.to_owned(), state.clone()); + status + .firmware + .insert(serial.to_owned(), state.status.clone()); } for (serial, device) in devices { diff --git a/daemon/src/servers/server_packet.rs b/daemon/src/servers/server_packet.rs index b0886690..357ec8bb 100644 --- a/daemon/src/servers/server_packet.rs +++ b/daemon/src/servers/server_packet.rs @@ -62,11 +62,11 @@ pub async fn handle_packet( Ok(DaemonResponse::Ok) } - DaemonRequest::RunFirmwareUpdate(serial, path) => { + DaemonRequest::RunFirmwareUpdate(serial, path, force) => { let (tx, rx) = oneshot::channel(); let path = path.map(PathBuf::from); usb_tx - .send(DeviceCommand::RunFirmwareUpdate(serial, path, tx)) + .send(DeviceCommand::RunFirmwareUpdate(serial, path, force, tx)) .await .map_err(anyhow::Error::msg)?; rx.await diff --git a/ipc/src/device.rs b/ipc/src/device.rs index b59e7b8c..b6172cac 100644 --- a/ipc/src/device.rs +++ b/ipc/src/device.rs @@ -18,7 +18,7 @@ use std::path::PathBuf; #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct DaemonStatus { pub config: DaemonConfig, - pub firmware: HashMap>, + pub firmware: HashMap, pub mixers: HashMap, pub paths: Paths, pub files: Files, diff --git a/ipc/src/lib.rs b/ipc/src/lib.rs index 4698a63f..f56b419d 100644 --- a/ipc/src/lib.rs +++ b/ipc/src/lib.rs @@ -9,12 +9,12 @@ mod device; pub use device::*; use goxlr_types::{ AnimationMode, Button, ButtonColourGroups, ButtonColourOffStyle, ChannelName, - CompressorAttackTime, CompressorRatio, CompressorReleaseTime, DisplayMode, + CompressorAttackTime, CompressorRatio, CompressorReleaseTime, DeviceType, DisplayMode, DisplayModeComponents, EchoStyle, EffectBankPresets, EncoderColourTargets, EqFrequencies, FaderDisplayStyle, FaderName, GateTimes, GenderStyle, HardTuneSource, HardTuneStyle, InputDevice, MegaphoneStyle, MicrophoneType, MiniEqFrequencies, Mix, MuteFunction, MuteState, OutputDevice, PitchStyle, ReverbStyle, RobotRange, RobotStyle, SampleBank, SampleButtons, - SamplePlayOrder, SamplePlaybackMode, SamplerColourTargets, SimpleColourTargets, + SamplePlayOrder, SamplePlaybackMode, SamplerColourTargets, SimpleColourTargets, VersionNumber, WaterfallDirection, }; @@ -25,7 +25,7 @@ pub enum DaemonRequest { Daemon(DaemonCommand), GetMicLevel(String), Command(String, GoXLRCommand), - RunFirmwareUpdate(String, Option), + RunFirmwareUpdate(String, Option, bool), ClearFirmwareState(String), } @@ -74,6 +74,7 @@ pub enum UpdateState { Starting, Manifest, Download, + Pause(FirmwareInfo), ClearNVR, UploadFirmware, ValidateUpload, @@ -82,6 +83,13 @@ pub enum UpdateState { Complete, } +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct FirmwareInfo { + pub path: PathBuf, + pub device_type: DeviceType, + pub version: VersionNumber, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, Eq, PartialEq)] pub enum LogLevel { Off,