mirror of
https://github.com/G2-Games/minidisc-cli.git
synced 2025-04-19 03:32:53 -05:00
Added proper error types
This commit is contained in:
parent
9bdd7ddb39
commit
e55736344d
7 changed files with 340 additions and 201 deletions
|
@ -34,6 +34,7 @@ cbc = "0.1"
|
|||
ecb = "0.1"
|
||||
tokio = { version = "1.36", features = ["sync"] }
|
||||
unicode-jp = { git = "https://github.com/uzabase/unicode-jp-rs.git" }
|
||||
thiserror = "1.0.57"
|
||||
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
gloo = { version = "0.11.0", features = ["futures"] }
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
/*!
|
||||
* This crate is an interface in rust to control NetMD and Hi-MD minidisc devices.
|
||||
*
|
||||
* Documentation coming soon
|
||||
*/
|
||||
/// A crate for controlling NetMD and Hi-MD devices.
|
||||
///
|
||||
/// To use this library, first you need to get a device from [cross-usb] and then open a [netmd::interface::NetMDInterface]
|
||||
|
||||
pub mod netmd;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
#![cfg_attr(debug_assertions, allow(dead_code))]
|
||||
use nofmt;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::error::Error;
|
||||
use std::time::Duration;
|
||||
|
||||
use nofmt;
|
||||
use once_cell::sync::Lazy;
|
||||
use thiserror::Error;
|
||||
|
||||
// USB stuff
|
||||
use cross_usb::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient};
|
||||
use cross_usb::usb::{ControlIn, ControlOut, ControlType, Device, Interface, Recipient, UsbError};
|
||||
use cross_usb::{UsbDevice, UsbInterface};
|
||||
|
||||
use super::utils::cross_sleep;
|
||||
|
||||
const DEFAULT_TIMEOUT: Duration = Duration::new(10000, 0);
|
||||
const BULK_WRITE_ENDPOINT: u8 = 0x02;
|
||||
const BULK_READ_ENDPOINT: u8 = 0x81;
|
||||
|
||||
|
@ -89,13 +89,32 @@ pub enum Status {
|
|||
}
|
||||
|
||||
/// The ID of a device, including the name
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct DeviceId {
|
||||
vendor_id: u16,
|
||||
product_id: u16,
|
||||
name: Option<String>,
|
||||
}
|
||||
|
||||
/// A connection to a NetMD device
|
||||
#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum NetMDError {
|
||||
#[error("communication timed out")]
|
||||
Timeout,
|
||||
|
||||
#[error("invalid usb result")]
|
||||
InvalidResult,
|
||||
|
||||
#[error("the device is not ready")]
|
||||
NotReady,
|
||||
|
||||
#[error("could not find device")]
|
||||
UnknownDevice(DeviceId),
|
||||
|
||||
#[error("usb connection error")]
|
||||
UsbError(#[from] UsbError),
|
||||
}
|
||||
|
||||
/// A USB connection to a NetMD device
|
||||
pub struct NetMD {
|
||||
usb_interface: UsbInterface,
|
||||
model: DeviceId,
|
||||
|
@ -105,7 +124,7 @@ impl NetMD {
|
|||
const READ_REPLY_RETRY_INTERVAL: u32 = 10;
|
||||
|
||||
/// Creates a new interface to a NetMD device
|
||||
pub async fn new(usb_device: &UsbDevice) -> Result<Self, Box<dyn Error>> {
|
||||
pub async fn new(usb_device: &UsbDevice) -> Result<Self, NetMDError> {
|
||||
let mut model = DeviceId {
|
||||
vendor_id: usb_device.vendor_id().await,
|
||||
product_id: usb_device.product_id().await,
|
||||
|
@ -122,7 +141,7 @@ impl NetMD {
|
|||
}
|
||||
|
||||
match model.name {
|
||||
None => return Err("Could not find device in list".into()),
|
||||
None => return Err(NetMDError::UnknownDevice(model)),
|
||||
Some(_) => (),
|
||||
}
|
||||
|
||||
|
@ -151,7 +170,7 @@ impl NetMD {
|
|||
|
||||
/// Poll the device to get either the result
|
||||
/// of the previous command, or the status
|
||||
pub async fn poll(&mut self) -> Result<(u16, [u8; 4]), Box<dyn Error>> {
|
||||
pub async fn poll(&mut self) -> Result<(u16, [u8; 4]), NetMDError> {
|
||||
// Create an array to store the result of the poll
|
||||
let poll_result = match self
|
||||
.usb_interface
|
||||
|
@ -173,17 +192,17 @@ impl NetMD {
|
|||
|
||||
let poll_result: [u8; 4] = match poll_result.try_into() {
|
||||
Ok(val) => val,
|
||||
Err(_) => return Err("could not convert result".into()),
|
||||
Err(_) => return Err(NetMDError::InvalidResult),
|
||||
};
|
||||
|
||||
Ok((length_bytes, poll_result))
|
||||
}
|
||||
|
||||
pub async fn send_command(&mut self, command: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
pub async fn send_command(&mut self, command: Vec<u8>) -> Result<(), NetMDError> {
|
||||
self._send_command(command, false).await
|
||||
}
|
||||
|
||||
pub async fn send_factory_command(&mut self, command: Vec<u8>) -> Result<(), Box<dyn Error>> {
|
||||
pub async fn send_factory_command(&mut self, command: Vec<u8>) -> Result<(), NetMDError> {
|
||||
self._send_command(command, true).await
|
||||
}
|
||||
|
||||
|
@ -192,12 +211,12 @@ impl NetMD {
|
|||
&mut self,
|
||||
command: Vec<u8>,
|
||||
use_factory_command: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
) -> Result<(), NetMDError> {
|
||||
// First poll to ensure the device is ready
|
||||
match self.poll().await {
|
||||
Ok(buffer) => match buffer.1[2] {
|
||||
0 => 0,
|
||||
_ => return Err("Device not ready!".into()),
|
||||
_ => return Err(NetMDError::NotReady),
|
||||
},
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
|
@ -227,14 +246,14 @@ impl NetMD {
|
|||
pub async fn read_reply(
|
||||
&mut self,
|
||||
override_length: Option<i32>,
|
||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
) -> Result<Vec<u8>, NetMDError> {
|
||||
self._read_reply(false, override_length).await
|
||||
}
|
||||
|
||||
pub async fn read_factory_reply(
|
||||
&mut self,
|
||||
override_length: Option<i32>,
|
||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
) -> Result<Vec<u8>, NetMDError> {
|
||||
self._read_reply(true, override_length).await
|
||||
}
|
||||
|
||||
|
@ -243,12 +262,12 @@ impl NetMD {
|
|||
&mut self,
|
||||
use_factory_command: bool,
|
||||
override_length: Option<i32>,
|
||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
) -> Result<Vec<u8>, NetMDError> {
|
||||
let mut length = 0;
|
||||
|
||||
for attempt in 0..40 {
|
||||
if attempt == 39 {
|
||||
return Err("Failed to get response length".into());
|
||||
return Err(NetMDError::Timeout);
|
||||
}
|
||||
|
||||
length = self.poll().await?.0;
|
||||
|
@ -260,7 +279,7 @@ impl NetMD {
|
|||
// Back off while trying again
|
||||
let sleep_time = Self::READ_REPLY_RETRY_INTERVAL * (u32::pow(2, attempt) - 1);
|
||||
|
||||
cross_sleep(sleep_time).await;
|
||||
cross_sleep(Duration::from_millis(sleep_time as u64)).await;
|
||||
}
|
||||
|
||||
if let Some(value) = override_length {
|
||||
|
@ -293,7 +312,7 @@ impl NetMD {
|
|||
&mut self,
|
||||
length: usize,
|
||||
chunksize: usize,
|
||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
) -> Result<Vec<u8>, NetMDError> {
|
||||
let result = self.read_bulk_to_array(length, chunksize).await?;
|
||||
|
||||
Ok(result)
|
||||
|
@ -303,7 +322,7 @@ impl NetMD {
|
|||
&mut self,
|
||||
length: usize,
|
||||
chunksize: usize,
|
||||
) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
) -> Result<Vec<u8>, NetMDError> {
|
||||
let mut final_result: Vec<u8> = Vec::new();
|
||||
let mut done = 0;
|
||||
|
||||
|
@ -317,7 +336,7 @@ impl NetMD {
|
|||
.await
|
||||
{
|
||||
Ok(result) => result,
|
||||
Err(error) => return Err(format!("USB error: {:?}", error).into()),
|
||||
Err(error) => return Err(NetMDError::UsbError(error)),
|
||||
};
|
||||
|
||||
final_result.extend_from_slice(&res);
|
||||
|
@ -326,7 +345,7 @@ impl NetMD {
|
|||
Ok(final_result)
|
||||
}
|
||||
|
||||
pub async fn write_bulk(&mut self, data: &[u8]) -> Result<usize, Box<dyn Error>> {
|
||||
pub async fn write_bulk(&mut self, data: &[u8]) -> Result<usize, NetMDError> {
|
||||
Ok(self
|
||||
.usb_interface
|
||||
.bulk_out(BULK_WRITE_ENDPOINT, data)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
use num_derive::FromPrimitive;
|
||||
use num_traits::FromPrimitive;
|
||||
use std::error::Error;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::interface::{MDSession, MDTrack, NetMDInterface};
|
||||
use super::utils::cross_sleep;
|
||||
|
@ -69,7 +70,7 @@ pub async fn prepare_download(interface: &mut NetMDInterface) -> Result<(), Box<
|
|||
.state
|
||||
.unwrap_or(OperatingStatus::NoDisc),
|
||||
) {
|
||||
cross_sleep(200).await;
|
||||
cross_sleep(Duration::from_millis(200)).await;
|
||||
}
|
||||
|
||||
let _ = interface.session_key_forget().await;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
use crate::netmd::utils;
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::hash_map::HashMap;
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
|
||||
lazy_static! {
|
||||
/// %b, w, d, q - explained above (can have endiannes overriden by '>' and '<' operators, f. ex. %>d %<q)
|
||||
|
@ -27,24 +27,73 @@ pub enum QueryValue {
|
|||
Array(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum ValueError {
|
||||
#[error("type mismatch: expected {expected}, got {actual}")]
|
||||
TypeMismatch {
|
||||
expected: String,
|
||||
actual: String
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryValue {
|
||||
pub fn to_vec(&self) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
pub fn from_array<const S: usize>(value: [u8; S]) -> Self {
|
||||
Self::Array(value.to_vec())
|
||||
}
|
||||
|
||||
pub fn to_array<const S: usize>(&self) -> Result<[u8; S], ValueError> {
|
||||
let mut array = [0u8; S];
|
||||
match self {
|
||||
QueryValue::Array(a) => Ok(a.to_vec()),
|
||||
_ => Err("QueryValue type mismatch! Expected Vec<u8>, got i64".into()),
|
||||
QueryValue::Array(a) => {
|
||||
for (i, byte) in a.iter().take(S).enumerate() {
|
||||
array[i] = *byte
|
||||
}
|
||||
Ok(array)
|
||||
},
|
||||
_ => Err(ValueError::TypeMismatch {
|
||||
expected: String::from("Vec<u8>"),
|
||||
actual: format!("{:?}", self)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_i64(&self) -> Result<i64, Box<dyn Error>> {
|
||||
pub fn to_vec(&self) -> Result<Vec<u8>, ValueError> {
|
||||
match self {
|
||||
QueryValue::Array(a) => Ok(a.to_vec()),
|
||||
_ => Err(ValueError::TypeMismatch {
|
||||
expected: String::from("Vec<u8>"),
|
||||
actual: format!("{:?}", self)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_i64(&self) -> Result<i64, ValueError> {
|
||||
match self {
|
||||
QueryValue::Number(a) => Ok(*a),
|
||||
_ => Err("QueryValue type mismatch! Expected i64, got Vec<u8>".into()),
|
||||
_ => Err(ValueError::TypeMismatch {
|
||||
expected: String::from("i64"),
|
||||
actual: format!("{:?}", self)
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub enum QueryError {
|
||||
#[error("unrecognized format character: `{0}`")]
|
||||
UnrecognizedChar(char),
|
||||
|
||||
#[error("Format and input mismatch at {index}: expected {expected:#04x}, got {actual:#04x} (format {format_string})")]
|
||||
InputMismatch {
|
||||
index: usize,
|
||||
expected: u8,
|
||||
actual: u8,
|
||||
format_string: String,
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats a query using a standard input to send to the player
|
||||
pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Box<dyn Error>> {
|
||||
pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, QueryError> {
|
||||
if DEBUG {
|
||||
println!("SENT>>> F: {}", format);
|
||||
}
|
||||
|
@ -114,7 +163,7 @@ pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Bo
|
|||
}
|
||||
result.push((converted & 0xFF) as u8);
|
||||
}
|
||||
_ => return Err(format!("Unrecognized format char {}", character).into()),
|
||||
_ => return Err(QueryError::UnrecognizedChar(character)),
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -142,7 +191,7 @@ pub fn format_query(format: String, args: Vec<QueryValue>) -> Result<Vec<u8>, Bo
|
|||
pub fn scan_query(
|
||||
query_result: Vec<u8>,
|
||||
format: String,
|
||||
) -> Result<Vec<QueryValue>, Box<dyn Error>> {
|
||||
) -> Result<Vec<QueryValue>, QueryError> {
|
||||
let mut result: Vec<QueryValue> = Vec::new();
|
||||
|
||||
let initial_length = query_result.len();
|
||||
|
@ -224,7 +273,7 @@ pub fn scan_query(
|
|||
| input_stack.next().unwrap() as i32;
|
||||
result.push(QueryValue::Number(utils::bcd_to_int(v) as i64));
|
||||
}
|
||||
_ => return Err(format!("Unrecognized format char {}", character).into()),
|
||||
_ => return Err(QueryError::UnrecognizedChar(character)),
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -244,7 +293,12 @@ pub fn scan_query(
|
|||
u8::from_str_radix(&String::from_iter([half.unwrap(), character]), 16).unwrap();
|
||||
if format_value != input_value {
|
||||
let i = initial_length - input_stack.len() - 1;
|
||||
return Err(format!("Format and input mismatch at {i}: expected {format_value:#04x}, got {input_value:#04x} (format {format})").into());
|
||||
return Err(QueryError::InputMismatch {
|
||||
index: i,
|
||||
expected: format_value,
|
||||
actual: input_value,
|
||||
format_string: format
|
||||
});
|
||||
}
|
||||
half = None;
|
||||
}
|
||||
|
|
|
@ -2,19 +2,19 @@ use crate::netmd::mappings::{ALLOWED_HW_KANA, MAPPINGS_DE, MAPPINGS_HW, MAPPINGS
|
|||
use diacritics;
|
||||
use encoding_rs::SHIFT_JIS;
|
||||
use regex::Regex;
|
||||
use std::{collections::hash_map::HashMap, error::Error, vec::IntoIter};
|
||||
use std::{collections::hash_map::HashMap, error::Error, vec::IntoIter, time::Duration};
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
|
||||
extern crate kana;
|
||||
use kana::*;
|
||||
|
||||
/// Sleep for a specified number of milliseconds on any platform
|
||||
pub async fn cross_sleep(millis: u32) {
|
||||
pub async fn cross_sleep(duration: Duration) {
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
std::thread::sleep(std::time::Duration::from_millis(millis as u64));
|
||||
std::thread::sleep(duration);
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
gloo::timers::future::TimeoutFuture::new(millis).await;
|
||||
gloo::timers::future::TimeoutFuture::new(duration.as_millis()).await;
|
||||
}
|
||||
|
||||
pub fn bcd_to_int(mut bcd: i32) -> i32 {
|
||||
|
|
Loading…
Reference in a new issue