mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
Add get file info to afc
This commit is contained in:
19
Cargo.lock
generated
19
Cargo.lock
generated
@@ -337,6 +337,15 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
@@ -1124,6 +1133,7 @@ dependencies = [
|
||||
"base64",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"env_logger 0.11.7",
|
||||
"futures",
|
||||
"indexmap",
|
||||
@@ -1464,6 +1474,15 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
|
||||
@@ -26,6 +26,7 @@ indexmap = { version = "2.7", features = ["serde"], optional = true }
|
||||
uuid = { version = "1.12", features = ["serde", "v4"], optional = true }
|
||||
async-recursion = { version = "1.1", optional = true }
|
||||
base64 = { version = "0.22", optional = true }
|
||||
chrono = { version = "0.4.40", optional = true, default_features = false }
|
||||
|
||||
serde_json = { version = "1", optional = true }
|
||||
json = { version = "0.12", optional = true }
|
||||
@@ -43,7 +44,7 @@ tun-rs = { version = "2.0.8", features = ["async_tokio"] }
|
||||
bytes = "1.10.1"
|
||||
|
||||
[features]
|
||||
afc = []
|
||||
afc = ["dep:chrono"]
|
||||
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
||||
debug_proxy = []
|
||||
dvt = ["dep:byteorder", "dep:ns-keyed-archive"]
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Jackson Coxson
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use errors::AfcError;
|
||||
use log::warn;
|
||||
use opcode::AfcOpcode;
|
||||
use packet::{AfcPacket, AfcPacketHeader};
|
||||
|
||||
@@ -17,6 +20,17 @@ pub struct AfcClient {
|
||||
package_number: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileInfo {
|
||||
pub size: usize,
|
||||
pub blocks: usize,
|
||||
pub creation: chrono::NaiveDateTime,
|
||||
pub modified: chrono::NaiveDateTime,
|
||||
pub st_nlink: String,
|
||||
pub st_ifmt: String,
|
||||
pub st_link_target: Option<String>,
|
||||
}
|
||||
|
||||
impl IdeviceService for AfcClient {
|
||||
fn service_name() -> &'static str {
|
||||
"com.apple.afc"
|
||||
@@ -54,7 +68,7 @@ impl AfcClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list(&mut self, path: impl Into<String>) -> Result<Vec<String>, IdeviceError> {
|
||||
pub async fn list_dir(&mut self, path: impl Into<String>) -> Result<Vec<String>, IdeviceError> {
|
||||
let path = path.into();
|
||||
let header_payload = path.as_bytes().to_vec();
|
||||
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
|
||||
@@ -86,6 +100,88 @@ impl AfcClient {
|
||||
Ok(strings)
|
||||
}
|
||||
|
||||
pub async fn get_file_info(
|
||||
&mut self,
|
||||
path: impl Into<String>,
|
||||
) -> Result<FileInfo, IdeviceError> {
|
||||
let path = path.into();
|
||||
let header_payload = path.as_bytes().to_vec();
|
||||
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
|
||||
|
||||
let header = AfcPacketHeader {
|
||||
magic: MAGIC,
|
||||
entire_len: header_len, // it's the same since the payload is empty for this
|
||||
header_payload_len: header_len,
|
||||
packet_num: self.package_number,
|
||||
operation: AfcOpcode::GetFileInfo,
|
||||
};
|
||||
self.package_number += 1;
|
||||
|
||||
let packet = AfcPacket {
|
||||
header,
|
||||
header_payload,
|
||||
payload: Vec::new(),
|
||||
};
|
||||
|
||||
self.send(packet).await?;
|
||||
let res = self.read().await?;
|
||||
|
||||
let strings: Vec<String> = res
|
||||
.payload
|
||||
.split(|b| *b == 0)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| String::from_utf8_lossy(s).into_owned())
|
||||
.collect();
|
||||
|
||||
let mut kvs: HashMap<String, String> = strings
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| (chunk[0].clone(), chunk[1].clone()))
|
||||
.collect();
|
||||
|
||||
let size = kvs
|
||||
.remove("st_size")
|
||||
.and_then(|x| x.parse::<usize>().ok())
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
let blocks = kvs
|
||||
.remove("st_blocks")
|
||||
.and_then(|x| x.parse::<usize>().ok())
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
|
||||
let creation = kvs
|
||||
.remove("st_birthtime")
|
||||
.and_then(|x| x.parse::<i64>().ok())
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
let creation = chrono::DateTime::from_timestamp_nanos(creation).naive_local();
|
||||
|
||||
let modified = kvs
|
||||
.remove("st_mtime")
|
||||
.and_then(|x| x.parse::<i64>().ok())
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
let modified = chrono::DateTime::from_timestamp_nanos(modified).naive_local();
|
||||
|
||||
let st_nlink = kvs
|
||||
.remove("st_nlink")
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
let st_ifmt = kvs
|
||||
.remove("st_ifmt")
|
||||
.ok_or(IdeviceError::AfcMissingFileAttribute)?;
|
||||
let st_link_target = kvs.remove("st_link_target");
|
||||
|
||||
if !kvs.is_empty() {
|
||||
warn!("File info kvs not empty: {kvs:?}");
|
||||
}
|
||||
|
||||
Ok(FileInfo {
|
||||
size,
|
||||
blocks,
|
||||
creation,
|
||||
modified,
|
||||
st_nlink,
|
||||
st_ifmt,
|
||||
st_link_target,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn read(&mut self) -> Result<AfcPacket, IdeviceError> {
|
||||
let res = AfcPacket::read(&mut self.idevice).await?;
|
||||
if res.header.operation == AfcOpcode::Status {
|
||||
|
||||
@@ -312,6 +312,10 @@ pub enum IdeviceError {
|
||||
#[error("invalid afc magic")]
|
||||
InvalidAfcMagic,
|
||||
|
||||
#[cfg(feature = "afc")]
|
||||
#[error("missing file attribute")]
|
||||
AfcMissingFileAttribute,
|
||||
|
||||
#[cfg(any(feature = "tss", feature = "tunneld"))]
|
||||
#[error("http reqwest error")]
|
||||
Reqwest(#[from] reqwest::Error),
|
||||
|
||||
@@ -46,6 +46,11 @@ async fn main() {
|
||||
.about("Remove a provisioning profile")
|
||||
.arg(Arg::new("path").required(true).index(1)),
|
||||
)
|
||||
.subcommand(
|
||||
Command::new("info")
|
||||
.about("Get info about a file")
|
||||
.arg(Arg::new("path").required(true).index(1)),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
if matches.get_flag("about") {
|
||||
@@ -71,10 +76,17 @@ async fn main() {
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("list") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
let res = afc_client.list(path).await.expect("Failed to read dir");
|
||||
let res = afc_client.list_dir(path).await.expect("Failed to read dir");
|
||||
println!("{path}\n{res:#?}");
|
||||
} else if let Some(matches) = matches.subcommand_matches("remove") {
|
||||
let path = matches.get_one::<String>("id").expect("No path passed");
|
||||
} else if let Some(matches) = matches.subcommand_matches("info") {
|
||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
||||
let res = afc_client
|
||||
.get_file_info(path)
|
||||
.await
|
||||
.expect("Failed to get file info");
|
||||
println!("{res:#?}");
|
||||
} else {
|
||||
eprintln!("Invalid usage, pass -h for help");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user