From 87cbc4d2dccf7e0511b73520925e84b2e1c1157f Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Sat, 2 Sep 2023 22:23:35 +0000 Subject: [PATCH] tools: Migrate `storage` and `run-fap` binaries to RPC session --- tools/src/bin/run-fap.rs | 49 +++--- tools/src/bin/storage.rs | 13 +- tools/src/storage.rs | 311 ++++++++++++++++++--------------------- 3 files changed, 175 insertions(+), 198 deletions(-) diff --git a/tools/src/bin/run-fap.rs b/tools/src/bin/run-fap.rs index 1352902c..268df916 100644 --- a/tools/src/bin/run-fap.rs +++ b/tools/src/bin/run-fap.rs @@ -7,7 +7,10 @@ use std::{ }; use clap::Parser; -use flipperzero_tools::{serial, storage}; +use flipperzero_tools::{ + proto::{pb, pb_app, RpcSession}, + serial, storage, +}; use rand::{thread_rng, Rng}; #[derive(Parser)] @@ -62,17 +65,10 @@ impl From for Error { } } -fn wait_for_idle(serial_cli: &mut serial::SerialCli) -> io::Result<()> { - loop { - serial_cli.send_and_wait_eol("loader info")?; - if serial_cli - .consume_response()? - .contains("No application is running") - { - break Ok(()); - } - thread::sleep(Duration::from_millis(200)); - } +fn wait_for_idle(session: &mut RpcSession) -> io::Result<()> { + // TODO: need equivalent of "loader info" CLI command. + thread::sleep(Duration::from_millis(2000)); + Ok(()) } fn main() -> Result<(), Error> { @@ -95,8 +91,8 @@ fn main() -> Result<(), Error> { .timeout(Duration::from_secs(30)) .open() .map_err(Error::FailedToOpenSerialPort)?; - let mut store = storage::FlipperStorage::new(port); - store.start().map_err(Error::FailedToStartSerialInterface)?; + let mut store = + storage::FlipperStorage::new(port).map_err(Error::FailedToStartSerialInterface)?; // Upload the FAP to a temporary directory. let dest_dir = @@ -109,31 +105,38 @@ fn main() -> Result<(), Error> { .send_file(&cli.fap, &dest_file) .map_err(Error::FailedToUploadFap)?; - let serial_cli = store.cli_mut(); + let session = store.session_mut(); // Wait for no application to be running. - wait_for_idle(serial_cli)?; + wait_for_idle(session)?; // Run the FAP. - serial_cli.send_and_wait_eol(&format!("loader open {} {}", dest_file, cli.args.join(" ")))?; + session.request( + 0, + pb::main::Content::AppStartRequest(pb_app::StartRequest { + name: dest_file.to_string(), + args: cli.args.join(" "), + }), + |resp| match resp { + pb::main::Content::Empty(_) => Ok(()), + r => Err(r), + }, + )?; // Wait for the FAP to finish. - wait_for_idle(serial_cli)?; + wait_for_idle(session)?; // Download and print the output file, if present. let output_file = storage::FlipperPath::from("/ext/flipperzero-rs-stdout"); if store.exist_file(&output_file)? { let output = store.read_file(&output_file)?; io::stdout().write_all(output.as_ref())?; - store.remove(&output_file)?; + store.remove(&output_file, false)?; } // Remove the FAP and temporary directory. store - .remove(&dest_file) - .map_err(|e| Error::RemoveFailed(dest_file, e))?; - store - .remove(&dest_dir) + .remove(&dest_dir, true) .map_err(|e| Error::RemoveFailed(dest_dir, e))?; Ok(()) diff --git a/tools/src/bin/storage.rs b/tools/src/bin/storage.rs index 2948c28a..a27ab8cd 100644 --- a/tools/src/bin/storage.rs +++ b/tools/src/bin/storage.rs @@ -31,9 +31,9 @@ enum Commands { /// Flipper path flipper_path: FlipperPath, }, - /// Format flash card - Format, - /// Remove file/directory + // /// Format flash card + // Format, + // /// Remove file/directory Remove { /// Flipper path flipper_path: FlipperPath, @@ -90,13 +90,12 @@ fn main() { .open() .expect("unable to open serial port"); - let mut store = storage::FlipperStorage::new(port); - store.start().expect("failed to start storage"); + let mut store = storage::FlipperStorage::new(port).unwrap(); let result = match command { Commands::Mkdir { flipper_path } => store.mkdir(flipper_path), - Commands::Format => store.format_ext(), - Commands::Remove { flipper_path } => store.remove(flipper_path), + // Commands::Format => store.format_ext(), + Commands::Remove { flipper_path } => store.remove(flipper_path, true), Commands::Read => todo!(), Commands::Size { flipper_path } => match store.size(flipper_path) { Err(err) => Err(err), diff --git a/tools/src/storage.rs b/tools/src/storage.rs index a2f8779c..2283def6 100644 --- a/tools/src/storage.rs +++ b/tools/src/storage.rs @@ -7,87 +7,73 @@ use std::path::Path; use std::{fs, io}; use bytes::BytesMut; -use regex::Regex; use serialport::SerialPort; -use crate::serial::{SerialCli, CLI_EOL}; +use crate::{ + proto::{pb, pb_storage, RpcSession}, + serial::SerialCli, +}; const BUF_SIZE: usize = 1024; /// Interface to Flipper device storage. pub struct FlipperStorage { - cli: SerialCli, + session: RpcSession, } impl FlipperStorage { /// Create new [`FlipperStorage`] connected to a [`SerialPort`]. - pub fn new(port: Box) -> Self { - Self { - cli: SerialCli::new(port), - } - } - - /// Start serial interface. - pub fn start(&mut self) -> io::Result<()> { - self.cli.start() - } + pub fn new(port: Box) -> io::Result { + let mut cli = SerialCli::new(port); + cli.start()?; - /// Get reference to underlying [`SerialPort`]. - pub fn port(&self) -> &dyn SerialPort { - self.cli.port() + Ok(Self { + session: cli.start_rpc_session()?, + }) } - /// Get mutable reference to underlying [`SerialPort`]. - pub fn port_mut(&mut self) -> &mut dyn SerialPort { - self.cli.port_mut() - } - - /// Get mutable reference to underlying [`SerialCli`]. - pub fn cli_mut(&mut self) -> &mut SerialCli { - &mut self.cli + /// Returns a mutable reference to the underlying [`RpcSession`]. + pub fn session_mut(&mut self) -> &mut RpcSession { + &mut self.session } /// List files and directories on the device. pub fn list_tree(&mut self, path: &FlipperPath) -> io::Result<()> { // Note: The `storage list` command expects that paths do not end with a slash. - self.cli - .send_and_wait_eol(&format!("storage list {}", path))?; - - let data = self.cli.read_until_prompt()?; - for line in CLI_EOL.split(&data).map(String::from_utf8_lossy) { - let line = line.trim(); - if line.is_empty() { - continue; - } - - if let Some(error) = SerialCli::get_error(line) { - eprintln!("ERROR: {error}"); - continue; - } - - if line == "Empty" { - continue; - } + let files = { + let mut files = vec![]; + self.session.request_many( + 0, + pb::main::Content::StorageListRequest(pb_storage::ListRequest { + path: path.to_string(), + include_md5: false, + filter_max_size: u32::MAX, + }), + |resp| { + Ok(match resp { + pb::main::Content::StorageListResponse(resp) => { + files.extend(resp.file); + Ok(()) + } + r => Err(r), + }) + }, + )?; + files + }; - if let Some((typ, info)) = line.split_once(' ') { - match typ { - // Directory - "[D]" => { - let path = path.clone() + info; + for f in files { + match f.r#type() { + pb_storage::file::FileType::Dir => { + let path = path.clone() + f.name.as_str(); - eprintln!("{path}"); - self.list_tree(&path)?; - } - // File - "[F]" => { - if let Some((name, size)) = info.rsplit_once(' ') { - let path = path.clone() + name; + eprintln!("{path}"); + self.list_tree(&path)?; + } + pb_storage::file::FileType::File => { + let path = path.clone() + f.name.as_str(); - eprintln!("{path}, size {size}"); - } - } - // We got something unexpected, ignore it - _ => (), + eprintln!("{path}, size {}", f.size); } } } @@ -101,33 +87,24 @@ impl FlipperStorage { if let Some(dir) = to.0.rsplit_once('/') { self.mkdir(&FlipperPath::from(dir.0)).ok(); } - self.remove(to).ok(); - - let mut file = fs::File::open(from.as_ref())?; - - let mut buf = [0u8; BUF_SIZE]; - loop { - let n = file.read(&mut buf)?; - if n == 0 { - break; - } - - self.cli - .send_and_wait_eol(&format!("storage write_chunk \"{to}\" {n}"))?; - let line = self.cli.read_until_eol()?; - let line = String::from_utf8_lossy(&line); - - if let Some(error) = SerialCli::get_error(&line) { - self.cli.read_until_prompt()?; - - return Err(io::Error::new(io::ErrorKind::Other, error)); - } - - self.port_mut().write_all(&buf[..n])?; - self.cli.read_until_prompt()?; - } - - Ok(()) + self.remove(to, false).ok(); + + let mut file = pb_storage::File::default(); + file.set_type(pb_storage::file::FileType::File); + + fs::File::open(from.as_ref())?.read_to_end(&mut file.data)?; + + self.session.request( + 0, + pb::main::Content::StorageWriteRequest(pb_storage::WriteRequest { + path: to.to_string(), + file: Some(file), + }), + |resp| match resp { + pb::main::Content::Empty(_) => Ok(()), + r => Err(r), + }, + ) } /// Receive remote file from the device. @@ -145,124 +122,122 @@ impl FlipperStorage { /// Read file data from the device. pub fn read_file(&mut self, path: &FlipperPath) -> io::Result { - self.cli - .send_and_wait_eol(&format!("storage read_chunks \"{path}\" {}", BUF_SIZE))?; - let line = self.cli.read_until_eol()?; - let line = String::from_utf8_lossy(&line); - - if let Some(error) = SerialCli::get_error(&line) { - self.cli.read_until_prompt()?; - - return Err(io::Error::new(io::ErrorKind::Other, error)); - } - - let (_, size) = line - .split_once(": ") - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "failed to read chunk size"))?; - let size: usize = size - .parse() - .map_err(|_| io::Error::new(io::ErrorKind::Other, "failed to parse chunk size"))?; - let mut data = BytesMut::with_capacity(BUF_SIZE); - let mut buf = [0u8; BUF_SIZE]; - while data.len() < size { - self.cli.read_until_ready()?; - self.cli.send_line("y")?; - - let n = (size - data.len()).min(BUF_SIZE); - self.port_mut().read_exact(&mut buf[..n])?; - data.extend_from_slice(&buf[..n]); - } + self.session.request_many( + 0, + pb::main::Content::StorageReadRequest(pb_storage::ReadRequest { + path: path.to_string(), + }), + |resp| { + Ok(match resp { + pb::main::Content::StorageReadResponse(resp) => { + let file = resp.file.ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "file does not exist") + })?; + data.extend(file.data); + Ok(()) + } + r => Err(r), + }) + }, + )?; Ok(data) } /// Does the file or directory exist on the device? pub fn exist(&mut self, path: &FlipperPath) -> io::Result { - let exist = match self.stat(path) { - Err(_err) => false, - Ok(_) => true, - }; - - Ok(exist) + self.stat(path).map(|f| f.is_some()) } /// Does the directory exist on the device? pub fn exist_dir(&mut self, path: &FlipperPath) -> io::Result { - let exist = match self.stat(path) { - Err(_err) => false, - Ok(stat) => stat.contains("Directory") || stat.contains("Storage"), - }; - - Ok(exist) + self.stat(path).map(|stat| match stat { + Some(f) => matches!(f.r#type(), pb_storage::file::FileType::Dir), + None => false, + }) } /// Does the file exist on the device? pub fn exist_file(&mut self, path: &FlipperPath) -> io::Result { - let exist = match self.stat(path) { - Err(_err) => false, - Ok(stat) => stat.contains("File, size:"), - }; - - Ok(exist) + self.stat(path).map(|stat| match stat { + Some(f) => matches!(f.r#type(), pb_storage::file::FileType::File), + None => false, + }) } /// File size in bytes pub fn size(&mut self, path: &FlipperPath) -> io::Result { - let line = self.stat(path)?; - - let size = Regex::new(r"File, size: (.+)b") - .unwrap() - .captures(&line) - .and_then(|m| m[1].parse::().ok()) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "failed to parse size"))?; - - Ok(size) + self.stat(path)? + .map(|f| f.size as usize) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "file does not exist")) } /// Stat a file or directory. - fn stat(&mut self, path: &FlipperPath) -> io::Result { - self.cli - .send_and_wait_eol(&format!("storage stat {path}"))?; - let line = self.cli.consume_response()?; - - Ok(line) + fn stat(&mut self, path: &FlipperPath) -> io::Result> { + self.session.request( + 0, + pb::main::Content::StorageStatRequest(pb_storage::StatRequest { + path: path.to_string(), + }), + |resp| match resp { + pb::main::Content::StorageStatResponse(resp) => Ok(resp.file), + r => Err(r), + }, + ) } /// Make directory on the device. pub fn mkdir(&mut self, path: &FlipperPath) -> io::Result<()> { - self.cli - .send_and_wait_eol(&format!("storage mkdir {path}"))?; - self.cli.consume_response()?; - - Ok(()) + self.session.request( + 0, + pb::main::Content::StorageMkdirRequest(pb_storage::MkdirRequest { + path: path.to_string(), + }), + |resp| match resp { + pb::main::Content::Empty(_) => Ok(()), + r => Err(r), + }, + ) } - /// Format external storage. - pub fn format_ext(&mut self) -> io::Result<()> { - self.cli.send_and_wait_eol("storage format /ext")?; - self.cli.send_and_wait_eol("y")?; - self.cli.consume_response()?; + // /// Format external storage. + // pub fn format_ext(&mut self) -> io::Result<()> { + // self.cli.send_and_wait_eol("storage format /ext")?; + // self.cli.send_and_wait_eol("y")?; + // self.cli.consume_response()?; - Ok(()) - } + // Ok(()) + // } /// Remove file or directory. - pub fn remove(&mut self, path: &FlipperPath) -> io::Result<()> { - self.cli - .send_and_wait_eol(&format!("storage remove {path}"))?; - self.cli.consume_response()?; - - Ok(()) + pub fn remove(&mut self, path: &FlipperPath, recursive: bool) -> io::Result<()> { + self.session.request( + 0, + pb::main::Content::StorageDeleteRequest(pb_storage::DeleteRequest { + path: path.to_string(), + recursive, + }), + |resp| match resp { + pb::main::Content::Empty(_) => Ok(()), + r => Err(r), + }, + ) } /// Calculate MD5 hash of file. pub fn md5sum(&mut self, path: &FlipperPath) -> io::Result { - self.cli.send_and_wait_eol(&format!("storage md5 {path}"))?; - let line = self.cli.consume_response()?; - - Ok(line) + self.session.request( + 0, + pb::main::Content::StorageMd5sumRequest(pb_storage::Md5sumRequest { + path: path.to_string(), + }), + |resp| match resp { + pb::main::Content::StorageMd5sumResponse(resp) => Ok(resp.md5sum), + r => Err(r), + }, + ) } }