Sync functionality (#11)
* Sync functionality Added the following sync commands: SEND - Working as intended RECV - Working as intended STAT - Only checks if file exist LIST - Untested due to device issues --------- Co-authored-by: Fredrik Jagenheim <fjagenheim@jabra.com> Co-authored-by: LIAUD Corentin <corentin.liaud@orange.fr>
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,10 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.5.0] - 2023-04-07
|
||||
|
||||
- [Breaking] Commands previously using `serial` argument now takes `&Option<S: ToString>` instead of `Option<String>`. (#8)
|
||||
- Adds `serial` argument for `host-feature` command. (#8)
|
||||
|
||||
Thanks @jagenheim for contributing !
|
||||
@@ -17,9 +17,11 @@ name = "adb_cli"
|
||||
path = "examples/adb_cli.rs"
|
||||
|
||||
[dependencies]
|
||||
byteorder = { version = "1.4.3" }
|
||||
chrono = { version = "0.4.26" }
|
||||
regex = { version = "1.9.3", features = ["perf", "std", "unicode"] }
|
||||
termios = { version = "0.3.3" }
|
||||
thiserror = { version = "1.0.44" }
|
||||
thiserror = { version = "1.0.46" }
|
||||
|
||||
## Binary-only dependencies
|
||||
## Marked as optional so that lib users do not depend on them
|
||||
|
||||
@@ -48,7 +48,6 @@ cargo install adb_client --example adb_cli
|
||||
|
||||
## Missing features
|
||||
|
||||
- Pull / Push files
|
||||
- USB protocol
|
||||
|
||||
All pull requests are welcome !
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::fs::File;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::path::Path;
|
||||
|
||||
use adb_client::{AdbTcpConnexion, Device, RebootType, RustADBError};
|
||||
use clap::Parser;
|
||||
@@ -34,6 +36,14 @@ pub enum Command {
|
||||
TrackDevices,
|
||||
/// Lists available server features.
|
||||
HostFeatures,
|
||||
/// Pushes 'filename' to the 'path' on device
|
||||
Push { filename: String, path: String },
|
||||
/// Pushes 'path' on the device to 'filename'
|
||||
Pull { path: String, filename: String },
|
||||
/// List files for 'path' on device
|
||||
List { path: String },
|
||||
/// Stat file specified as 'path' on device
|
||||
Stat { path: String },
|
||||
/// Run 'command' in a shell on the device, and return its output and error streams.
|
||||
Shell { command: Vec<String> },
|
||||
/// Reboots the device
|
||||
@@ -99,6 +109,23 @@ fn main() -> Result<(), RustADBError> {
|
||||
println!("Live list of devices attached");
|
||||
connexion.track_devices(callback)?;
|
||||
}
|
||||
Command::Pull { path, filename } => {
|
||||
let mut output = File::create(Path::new(&filename)).unwrap(); // TODO: Better error handling
|
||||
connexion.recv(opt.serial, &path, &mut output)?;
|
||||
println!("Downloaded {path} as {filename}");
|
||||
}
|
||||
Command::Push { filename, path } => {
|
||||
let mut input = File::open(Path::new(&filename)).unwrap(); // TODO: Better error handling
|
||||
connexion.send(opt.serial, &mut input, &path)?;
|
||||
println!("Uploaded {filename} to {path}");
|
||||
}
|
||||
Command::List { path } => {
|
||||
connexion.list(opt.serial, path)?;
|
||||
}
|
||||
Command::Stat { path } => {
|
||||
let stat_response = connexion.stat(opt.serial, path)?;
|
||||
println!("{}", stat_response);
|
||||
}
|
||||
Command::Shell { command } => {
|
||||
if command.is_empty() {
|
||||
connexion.shell(&opt.serial)?;
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
models::{AdbCommand, AdbRequestStatus},
|
||||
models::{AdbCommand, AdbRequestStatus, SyncCommand},
|
||||
Result, RustADBError,
|
||||
};
|
||||
|
||||
@@ -41,10 +41,10 @@ impl AdbTcpConnexion {
|
||||
adb_command: AdbCommand,
|
||||
with_response: bool,
|
||||
) -> Result<Vec<u8>> {
|
||||
Self::send_adb_request(&mut self.tcp_stream, adb_command)?;
|
||||
self.send_adb_request(adb_command)?;
|
||||
|
||||
if with_response {
|
||||
let length = Self::get_body_length(&mut self.tcp_stream)?;
|
||||
let length = self.get_body_length()?;
|
||||
let mut body = vec![
|
||||
0;
|
||||
length
|
||||
@@ -63,20 +63,20 @@ impl AdbTcpConnexion {
|
||||
|
||||
/// Sends the given [AdbCommand] to ADB server, and checks that the request has been taken in consideration.
|
||||
/// If an error occured, a [RustADBError] is returned with the response error string.
|
||||
pub(crate) fn send_adb_request(tcp_stream: &mut TcpStream, command: AdbCommand) -> Result<()> {
|
||||
pub(crate) fn send_adb_request(&mut self, command: AdbCommand) -> Result<()> {
|
||||
let adb_command_string = command.to_string();
|
||||
let adb_request = format!("{:04x}{}", adb_command_string.len(), adb_command_string);
|
||||
|
||||
tcp_stream.write_all(adb_request.as_bytes())?;
|
||||
self.tcp_stream.write_all(adb_request.as_bytes())?;
|
||||
|
||||
// Reads returned status code from ADB server
|
||||
let mut request_status = [0; 4];
|
||||
tcp_stream.read_exact(&mut request_status)?;
|
||||
self.tcp_stream.read_exact(&mut request_status)?;
|
||||
|
||||
match AdbRequestStatus::from_str(str::from_utf8(request_status.as_ref())?)? {
|
||||
AdbRequestStatus::Fail => {
|
||||
// We can keep reading to get further details
|
||||
let length = Self::get_body_length(tcp_stream)?;
|
||||
let length = self.get_body_length()?;
|
||||
|
||||
let mut body = vec![
|
||||
0;
|
||||
@@ -85,7 +85,7 @@ impl AdbTcpConnexion {
|
||||
.map_err(|_| RustADBError::ConvertionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
tcp_stream.read_exact(&mut body)?;
|
||||
self.tcp_stream.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?))
|
||||
@@ -94,9 +94,16 @@ impl AdbTcpConnexion {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_body_length(tcp_stream: &mut TcpStream) -> Result<u32> {
|
||||
/// Sends the given [SyncCommand] to ADB server, and checks that the request has been taken in consideration.
|
||||
pub(crate) fn send_sync_request(&mut self, command: SyncCommand) -> Result<()> {
|
||||
// First 4 bytes are the name of the command we want to send
|
||||
// (e.g. "SEND", "RECV", "STAT", "LIST")
|
||||
Ok(self.tcp_stream.write_all(command.to_string().as_bytes())?)
|
||||
}
|
||||
|
||||
pub(crate) fn get_body_length(&mut self) -> Result<u32> {
|
||||
let mut length = [0; 4];
|
||||
tcp_stream.read_exact(&mut length)?;
|
||||
self.tcp_stream.read_exact(&mut length)?;
|
||||
|
||||
Ok(u32::from_str_radix(str::from_utf8(&length)?, 16)?)
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ impl AdbTcpConnexion {
|
||||
/// Tracks new devices showing up.
|
||||
// TODO: Change with Generator when feature stabilizes
|
||||
pub fn track_devices(&mut self, callback: impl Fn(Device) -> Result<()>) -> Result<()> {
|
||||
Self::send_adb_request(&mut self.tcp_stream, AdbCommand::TrackDevices)?;
|
||||
self.send_adb_request(AdbCommand::TrackDevices)?;
|
||||
|
||||
loop {
|
||||
let length = Self::get_body_length(&mut self.tcp_stream)?;
|
||||
let length = self.get_body_length()?;
|
||||
|
||||
if length > 0 {
|
||||
let mut body = vec![
|
||||
|
||||
@@ -7,11 +7,10 @@ impl AdbTcpConnexion {
|
||||
/// Lists available ADB server features.
|
||||
pub fn host_features<S: ToString>(&mut self, serial: &Option<S>) -> Result<Vec<HostFeatures>> {
|
||||
match serial {
|
||||
None => Self::send_adb_request(&mut self.tcp_stream, AdbCommand::TransportAny)?,
|
||||
Some(serial) => Self::send_adb_request(
|
||||
&mut self.tcp_stream,
|
||||
AdbCommand::TransportSerial(serial.to_string()),
|
||||
)?,
|
||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
let features = self.proxy_connexion(AdbCommand::HostFeatures, true)?;
|
||||
|
||||
72
src/commands/list.rs
Normal file
72
src/commands/list.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, SyncCommand},
|
||||
AdbTcpConnexion, Result,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
str,
|
||||
};
|
||||
|
||||
impl AdbTcpConnexion {
|
||||
/// Lists files in [path] on the device.
|
||||
pub fn list<S: ToString, A: AsRef<str>>(&mut self, serial: Option<S>, path: A) -> Result<()> {
|
||||
self.new_connection()?;
|
||||
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.send_adb_request(AdbCommand::Sync)?;
|
||||
|
||||
// Send a list command
|
||||
self.send_sync_request(SyncCommand::List(path.as_ref()))?;
|
||||
|
||||
self.handle_list_command(path)
|
||||
}
|
||||
|
||||
// This command does not seem to work correctly. The devices I test it on just resturn
|
||||
// 'DONE' directly without listing anything.
|
||||
fn handle_list_command<S: AsRef<str>>(&mut self, path: S) -> Result<()> {
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, path.as_ref().len() as u32);
|
||||
|
||||
// 4 bytes of command name is already sent by send_sync_request
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
|
||||
// List sends the string of the directory to list, and then the server sends a list of files
|
||||
self.tcp_stream
|
||||
.write_all(path.as_ref().to_string().as_bytes())?;
|
||||
|
||||
// Reads returned status code from ADB server
|
||||
let mut response = [0_u8; 4];
|
||||
loop {
|
||||
self.tcp_stream.read_exact(&mut response)?;
|
||||
match str::from_utf8(response.as_ref())? {
|
||||
"DENT" => {
|
||||
// TODO: Move this to a struct that extract this data, but as the device
|
||||
// I test this on does not return anything, I can't test it.
|
||||
let mut file_mod = [0_u8; 4];
|
||||
let mut file_size = [0_u8; 4];
|
||||
let mut mod_time = [0_u8; 4];
|
||||
let mut name_len = [0_u8; 4];
|
||||
self.tcp_stream.read_exact(&mut file_mod)?;
|
||||
self.tcp_stream.read_exact(&mut file_size)?;
|
||||
self.tcp_stream.read_exact(&mut mod_time)?;
|
||||
self.tcp_stream.read_exact(&mut name_len)?;
|
||||
let name_len = LittleEndian::read_u32(&name_len);
|
||||
let mut name_buf = vec![0_u8; name_len as usize];
|
||||
self.tcp_stream.read_exact(&mut name_buf)?;
|
||||
}
|
||||
"DONE" => {
|
||||
return Ok(());
|
||||
}
|
||||
x => println!("Unknown response {}", x),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
mod devices;
|
||||
mod host_features;
|
||||
mod kill;
|
||||
mod list;
|
||||
mod reboot;
|
||||
mod recv;
|
||||
mod send;
|
||||
mod shell;
|
||||
mod stat;
|
||||
mod transport;
|
||||
mod version;
|
||||
|
||||
@@ -11,11 +11,10 @@ impl AdbTcpConnexion {
|
||||
reboot_type: RebootType,
|
||||
) -> Result<()> {
|
||||
match serial {
|
||||
None => Self::send_adb_request(&mut self.tcp_stream, AdbCommand::TransportAny)?,
|
||||
Some(serial) => Self::send_adb_request(
|
||||
&mut self.tcp_stream,
|
||||
AdbCommand::TransportSerial(serial.to_string()),
|
||||
)?,
|
||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
self.proxy_connexion(AdbCommand::Reboot(reboot_type), false)
|
||||
|
||||
77
src/commands/recv.rs
Normal file
77
src/commands/recv.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, SyncCommand},
|
||||
AdbTcpConnexion, Result, RustADBError,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::io::{Read, Write};
|
||||
|
||||
impl AdbTcpConnexion {
|
||||
/// Receives [path] to [stream] from the device.
|
||||
pub fn recv<S: ToString, A: AsRef<str>>(
|
||||
&mut self,
|
||||
serial: Option<S>,
|
||||
path: A,
|
||||
stream: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
self.new_connection()?;
|
||||
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.send_adb_request(AdbCommand::Sync)?;
|
||||
|
||||
// Send a recv command
|
||||
self.send_sync_request(SyncCommand::Recv(path.as_ref(), stream))?;
|
||||
|
||||
self.handle_recv_command(path, stream)
|
||||
}
|
||||
|
||||
fn handle_recv_command<S: AsRef<str>>(
|
||||
&mut self,
|
||||
from: S,
|
||||
output: &mut dyn Write,
|
||||
) -> Result<()> {
|
||||
// First send 8 byte common header
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, from.as_ref().len() as u32);
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
self.tcp_stream.write_all(from.as_ref().as_bytes())?;
|
||||
|
||||
// Then we receive the byte data in chunks of up to 64k
|
||||
// Chunk looks like 'DATA' <length> <data>
|
||||
let mut buffer = [0_u8; 64 * 1024]; // Should this be Boxed?
|
||||
let mut data_header = [0_u8; 4]; // DATA
|
||||
let mut len_header = [0_u8; 4]; // <len>
|
||||
loop {
|
||||
self.tcp_stream.read_exact(&mut data_header)?;
|
||||
// Check if data_header is DATA or DONE
|
||||
if data_header.eq(b"DATA") {
|
||||
self.tcp_stream.read_exact(&mut len_header)?;
|
||||
let length: usize = LittleEndian::read_u32(&len_header).try_into().unwrap();
|
||||
self.tcp_stream.read_exact(&mut buffer[..length])?;
|
||||
output.write_all(&buffer)?;
|
||||
} else if data_header.eq(b"DONE") {
|
||||
// We're done here
|
||||
break;
|
||||
} else if data_header.eq(b"FAIL") {
|
||||
// Handle fail
|
||||
self.tcp_stream.read_exact(&mut len_header)?;
|
||||
let length: usize = LittleEndian::read_u32(&len_header).try_into().unwrap();
|
||||
self.tcp_stream.read_exact(&mut buffer[..length])?;
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(
|
||||
buffer[..length].to_vec(),
|
||||
)?))?;
|
||||
} else {
|
||||
panic!("Unknown response from device {:#?}", data_header);
|
||||
}
|
||||
}
|
||||
|
||||
// Connection should've left SYNC by now
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
100
src/commands/send.rs
Normal file
100
src/commands/send.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use crate::{
|
||||
models::{AdbCommand, AdbRequestStatus, SyncCommand},
|
||||
AdbTcpConnexion, Result, RustADBError,
|
||||
};
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
io::{Read, Write},
|
||||
str::{self, FromStr},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
impl AdbTcpConnexion {
|
||||
/// Sends [stream] to [path] on the device.
|
||||
pub fn send<S: ToString, A: AsRef<str>>(
|
||||
&mut self,
|
||||
serial: Option<S>,
|
||||
stream: &mut dyn Read,
|
||||
path: A,
|
||||
) -> Result<()> {
|
||||
self.new_connection()?;
|
||||
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.send_adb_request(AdbCommand::Sync)?;
|
||||
|
||||
// Send a send command
|
||||
self.send_sync_request(SyncCommand::Send(stream, path.as_ref()))?;
|
||||
|
||||
self.handle_send_command(stream, path)
|
||||
}
|
||||
|
||||
fn handle_send_command<S: AsRef<str>>(&mut self, input: &mut dyn Read, to: S) -> Result<()> {
|
||||
// Append the permission flags to the filename
|
||||
let to = to.as_ref().to_string() + ",0777";
|
||||
|
||||
// The name of command is already sent by send_sync_request
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, to.len() as u32);
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
|
||||
// Send appends the filemode to the string sent
|
||||
self.tcp_stream.write_all(to.as_bytes())?;
|
||||
|
||||
// Then we send the byte data in chunks of up to 64k
|
||||
// Chunk looks like 'DATA' <length> <data>
|
||||
let mut buffer = [0_u8; 64 * 1024];
|
||||
loop {
|
||||
let bytes_read = input.read(&mut buffer)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
let mut chunk_len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut chunk_len_buf, bytes_read as u32);
|
||||
self.tcp_stream.write_all(b"DATA")?;
|
||||
self.tcp_stream.write_all(&chunk_len_buf)?;
|
||||
self.tcp_stream.write_all(&buffer[..bytes_read])?;
|
||||
}
|
||||
|
||||
// When we are done sending, we send 'DONE' <last modified time>
|
||||
// Re-use len_buf to send the last modified time
|
||||
let last_modified = match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
|
||||
Ok(n) => n,
|
||||
Err(_) => panic!("SystemTime before UNIX EPOCH!"),
|
||||
};
|
||||
LittleEndian::write_u32(&mut len_buf, last_modified.as_secs() as u32);
|
||||
self.tcp_stream.write_all(b"DONE")?;
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
|
||||
// We expect 'OKAY' response from this
|
||||
let mut request_status = [0; 4];
|
||||
self.tcp_stream.read_exact(&mut request_status)?;
|
||||
|
||||
match AdbRequestStatus::from_str(str::from_utf8(request_status.as_ref())?)? {
|
||||
AdbRequestStatus::Fail => {
|
||||
// We can keep reading to get further details
|
||||
let length = self.get_body_length()?;
|
||||
|
||||
let mut body = vec![
|
||||
0;
|
||||
length
|
||||
.try_into()
|
||||
.map_err(|_| RustADBError::ConvertionError)?
|
||||
];
|
||||
if length > 0 {
|
||||
self.tcp_stream.read_exact(&mut body)?;
|
||||
}
|
||||
|
||||
Err(RustADBError::ADBRequestFailed(String::from_utf8(body)?))
|
||||
}
|
||||
AdbRequestStatus::Okay => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,22 +23,18 @@ impl AdbTcpConnexion {
|
||||
self.new_connection()?;
|
||||
|
||||
match serial {
|
||||
None => Self::send_adb_request(&mut self.tcp_stream, AdbCommand::TransportAny)?,
|
||||
Some(serial) => Self::send_adb_request(
|
||||
&mut self.tcp_stream,
|
||||
AdbCommand::TransportSerial(serial.to_string()),
|
||||
)?,
|
||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
||||
}
|
||||
}
|
||||
Self::send_adb_request(
|
||||
&mut self.tcp_stream,
|
||||
AdbCommand::ShellCommand(
|
||||
command
|
||||
.into_iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
),
|
||||
)?;
|
||||
self.send_adb_request(AdbCommand::ShellCommand(
|
||||
command
|
||||
.into_iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
))?;
|
||||
|
||||
let buffer_size = 512;
|
||||
loop {
|
||||
@@ -66,7 +62,7 @@ impl AdbTcpConnexion {
|
||||
|
||||
self.tcp_stream.set_nodelay(true)?;
|
||||
|
||||
// FORWARD CRTL+C !!
|
||||
// FORWARD CTRL+C !!
|
||||
|
||||
let supported_features = self.host_features(serial)?;
|
||||
if !supported_features.contains(&HostFeatures::ShellV2)
|
||||
@@ -78,13 +74,12 @@ impl AdbTcpConnexion {
|
||||
self.new_connection()?;
|
||||
|
||||
match serial {
|
||||
None => Self::send_adb_request(&mut self.tcp_stream, AdbCommand::TransportAny)?,
|
||||
Some(serial) => Self::send_adb_request(
|
||||
&mut self.tcp_stream,
|
||||
AdbCommand::TransportSerial(serial.to_string()),
|
||||
)?,
|
||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
||||
}
|
||||
}
|
||||
Self::send_adb_request(&mut self.tcp_stream, AdbCommand::Shell)?;
|
||||
self.send_adb_request(AdbCommand::Shell)?;
|
||||
|
||||
// let read_stream = Arc::new(self.tcp_stream);
|
||||
let mut read_stream = self.tcp_stream.try_clone()?;
|
||||
|
||||
99
src/commands/stat.rs
Normal file
99
src/commands/stat.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use std::{
|
||||
fmt::Display,
|
||||
io::{Read, Write},
|
||||
time::{Duration, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::{
|
||||
models::{AdbCommand, SyncCommand},
|
||||
AdbTcpConnexion, Result, RustADBError,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AdbStatResponse {
|
||||
pub file_perm: u32,
|
||||
pub file_size: u32,
|
||||
pub mod_time: u32,
|
||||
}
|
||||
|
||||
impl From<[u8; 12]> for AdbStatResponse {
|
||||
fn from(value: [u8; 12]) -> Self {
|
||||
Self {
|
||||
file_perm: LittleEndian::read_u32(&value[0..4]),
|
||||
file_size: LittleEndian::read_u32(&value[4..8]),
|
||||
mod_time: LittleEndian::read_u32(&value[8..]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AdbStatResponse {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let d = UNIX_EPOCH + Duration::from_secs(self.mod_time.into());
|
||||
// Create DateTime from SystemTime
|
||||
let datetime = DateTime::<Utc>::from(d);
|
||||
|
||||
writeln!(f, "File permissions: {}", self.file_perm)?;
|
||||
writeln!(f, "File size: {} bytes", self.file_size)?;
|
||||
write!(
|
||||
f,
|
||||
"Modification time: {}",
|
||||
datetime.format("%Y-%m-%d %H:%M:%S.%f %Z")
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AdbTcpConnexion {
|
||||
fn handle_stat_command<S: AsRef<str>>(&mut self, path: S) -> Result<AdbStatResponse> {
|
||||
let mut len_buf = [0_u8; 4];
|
||||
LittleEndian::write_u32(&mut len_buf, path.as_ref().len() as u32);
|
||||
|
||||
// 4 bytes of command name is already sent by send_sync_request
|
||||
self.tcp_stream.write_all(&len_buf)?;
|
||||
self.tcp_stream
|
||||
.write_all(path.as_ref().to_string().as_bytes())?;
|
||||
|
||||
// Reads returned status code from ADB server
|
||||
let mut response = [0_u8; 4];
|
||||
self.tcp_stream.read_exact(&mut response)?;
|
||||
match std::str::from_utf8(response.as_ref())? {
|
||||
"STAT" => {
|
||||
let mut data = [0_u8; 12];
|
||||
self.tcp_stream.read_exact(&mut data)?;
|
||||
|
||||
Ok(data.into())
|
||||
}
|
||||
x => Err(RustADBError::UnknownResponseType(format!(
|
||||
"Unknown response {}",
|
||||
x
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
/// Stat file given as [path] on the device.
|
||||
pub fn stat<S: ToString, A: AsRef<str>>(
|
||||
&mut self,
|
||||
serial: Option<S>,
|
||||
path: A,
|
||||
) -> Result<AdbStatResponse> {
|
||||
self.new_connection()?;
|
||||
|
||||
match serial {
|
||||
None => self.send_adb_request(AdbCommand::TransportAny)?,
|
||||
Some(serial) => {
|
||||
self.send_adb_request(AdbCommand::TransportSerial(serial.to_string()))?
|
||||
}
|
||||
}
|
||||
|
||||
// Set device in SYNC mode
|
||||
self.send_adb_request(AdbCommand::Sync)?;
|
||||
|
||||
// Send a "Stat" command
|
||||
self.send_sync_request(SyncCommand::Stat(path.as_ref()))?;
|
||||
|
||||
self.handle_stat_command(path)
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ pub enum AdbCommand {
|
||||
// FrameBuffer,
|
||||
// JDWP(u32),
|
||||
// TrackJDWP,
|
||||
// Sync,
|
||||
Sync,
|
||||
// Reverse(String),
|
||||
Reboot(RebootType),
|
||||
}
|
||||
@@ -52,6 +52,7 @@ impl ToString for AdbCommand {
|
||||
AdbCommand::Kill => "host:kill".into(),
|
||||
AdbCommand::Devices => "host:devices".into(),
|
||||
AdbCommand::DevicesLong => "host:devices-l".into(),
|
||||
AdbCommand::Sync => "sync:".into(),
|
||||
AdbCommand::TrackDevices => "host:track-devices".into(),
|
||||
AdbCommand::TransportAny => "host:transport-any".into(),
|
||||
AdbCommand::TransportSerial(serial) => format!("host:transport:{serial}"),
|
||||
|
||||
@@ -6,6 +6,7 @@ mod device_long;
|
||||
mod device_state;
|
||||
mod host_features;
|
||||
mod reboot_type;
|
||||
mod sync_command;
|
||||
|
||||
pub use adb_command::AdbCommand;
|
||||
pub use adb_request_status::AdbRequestStatus;
|
||||
@@ -15,3 +16,4 @@ pub use device_long::DeviceLong;
|
||||
pub use device_state::DeviceState;
|
||||
pub use host_features::HostFeatures;
|
||||
pub use reboot_type::RebootType;
|
||||
pub use sync_command::SyncCommand;
|
||||
|
||||
22
src/models/sync_command.rs
Normal file
22
src/models/sync_command.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
pub enum SyncCommand<'a> {
|
||||
/// List files in a folder
|
||||
List(&'a str),
|
||||
/// Receive a file from the device
|
||||
Recv(&'a str, &'a mut dyn std::io::Write),
|
||||
/// Send a file to the device
|
||||
Send(&'a mut dyn std::io::Read, &'a str),
|
||||
// Stat a file
|
||||
Stat(&'a str),
|
||||
}
|
||||
|
||||
impl ToString for SyncCommand<'_> {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
SyncCommand::List(_) => "LIST",
|
||||
SyncCommand::Recv(_, _) => "RECV",
|
||||
SyncCommand::Send(_, _) => "SEND",
|
||||
SyncCommand::Stat(_) => "STAT",
|
||||
}
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user