mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 14:36:16 +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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
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]]
|
[[package]]
|
||||||
name = "clang-sys"
|
name = "clang-sys"
|
||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
@@ -1124,6 +1133,7 @@ dependencies = [
|
|||||||
"base64",
|
"base64",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"env_logger 0.11.7",
|
"env_logger 0.11.7",
|
||||||
"futures",
|
"futures",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -1464,6 +1474,15 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
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]]
|
[[package]]
|
||||||
name = "num_threads"
|
name = "num_threads"
|
||||||
version = "0.1.7"
|
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 }
|
uuid = { version = "1.12", features = ["serde", "v4"], optional = true }
|
||||||
async-recursion = { version = "1.1", optional = true }
|
async-recursion = { version = "1.1", optional = true }
|
||||||
base64 = { version = "0.22", optional = true }
|
base64 = { version = "0.22", optional = true }
|
||||||
|
chrono = { version = "0.4.40", optional = true, default_features = false }
|
||||||
|
|
||||||
serde_json = { version = "1", optional = true }
|
serde_json = { version = "1", optional = true }
|
||||||
json = { version = "0.12", 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"
|
bytes = "1.10.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
afc = []
|
afc = ["dep:chrono"]
|
||||||
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
||||||
debug_proxy = []
|
debug_proxy = []
|
||||||
dvt = ["dep:byteorder", "dep:ns-keyed-archive"]
|
dvt = ["dep:byteorder", "dep:ns-keyed-archive"]
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use errors::AfcError;
|
use errors::AfcError;
|
||||||
|
use log::warn;
|
||||||
use opcode::AfcOpcode;
|
use opcode::AfcOpcode;
|
||||||
use packet::{AfcPacket, AfcPacketHeader};
|
use packet::{AfcPacket, AfcPacketHeader};
|
||||||
|
|
||||||
@@ -17,6 +20,17 @@ pub struct AfcClient {
|
|||||||
package_number: u64,
|
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 {
|
impl IdeviceService for AfcClient {
|
||||||
fn service_name() -> &'static str {
|
fn service_name() -> &'static str {
|
||||||
"com.apple.afc"
|
"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 path = path.into();
|
||||||
let header_payload = path.as_bytes().to_vec();
|
let header_payload = path.as_bytes().to_vec();
|
||||||
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
|
let header_len = header_payload.len() as u64 + AfcPacketHeader::LEN;
|
||||||
@@ -86,6 +100,88 @@ impl AfcClient {
|
|||||||
Ok(strings)
|
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> {
|
pub async fn read(&mut self) -> Result<AfcPacket, IdeviceError> {
|
||||||
let res = AfcPacket::read(&mut self.idevice).await?;
|
let res = AfcPacket::read(&mut self.idevice).await?;
|
||||||
if res.header.operation == AfcOpcode::Status {
|
if res.header.operation == AfcOpcode::Status {
|
||||||
|
|||||||
@@ -312,6 +312,10 @@ pub enum IdeviceError {
|
|||||||
#[error("invalid afc magic")]
|
#[error("invalid afc magic")]
|
||||||
InvalidAfcMagic,
|
InvalidAfcMagic,
|
||||||
|
|
||||||
|
#[cfg(feature = "afc")]
|
||||||
|
#[error("missing file attribute")]
|
||||||
|
AfcMissingFileAttribute,
|
||||||
|
|
||||||
#[cfg(any(feature = "tss", feature = "tunneld"))]
|
#[cfg(any(feature = "tss", feature = "tunneld"))]
|
||||||
#[error("http reqwest error")]
|
#[error("http reqwest error")]
|
||||||
Reqwest(#[from] reqwest::Error),
|
Reqwest(#[from] reqwest::Error),
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ async fn main() {
|
|||||||
.about("Remove a provisioning profile")
|
.about("Remove a provisioning profile")
|
||||||
.arg(Arg::new("path").required(true).index(1)),
|
.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();
|
.get_matches();
|
||||||
|
|
||||||
if matches.get_flag("about") {
|
if matches.get_flag("about") {
|
||||||
@@ -71,10 +76,17 @@ async fn main() {
|
|||||||
|
|
||||||
if let Some(matches) = matches.subcommand_matches("list") {
|
if let Some(matches) = matches.subcommand_matches("list") {
|
||||||
let path = matches.get_one::<String>("path").expect("No path passed");
|
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:#?}");
|
println!("{path}\n{res:#?}");
|
||||||
} else if let Some(matches) = matches.subcommand_matches("remove") {
|
} else if let Some(matches) = matches.subcommand_matches("remove") {
|
||||||
let path = matches.get_one::<String>("id").expect("No path passed");
|
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 {
|
} else {
|
||||||
eprintln!("Invalid usage, pass -h for help");
|
eprintln!("Invalid usage, pass -h for help");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user